Comment faire un C# timer qui déclenche des événements dans le thread principal?

pour faire court, j'ai besoin d'un minuteur précis dans .Net - avec une précision en millisecondes - ce qui signifie, Si je lui dis de virer un événement quand 10ms passent, il doit le faire, +-1ms. La classe .net Timer intégrée a une précision de +-16ms, ce qui est inacceptable pour mon application.

j'ai trouvé cet article http://www.codeproject.com/Articles/98346/Microsecond-and-Millisecond-NET-Timer qui fournit un code pour une minuterie qui est exactement ce dont j'ai besoin (encore plus - que a une précision en microsecondes).

cependant, le problème est que l'équivalent OnTimer semble être exécuté dans un autre thread. Donc, si j'ajoute un code qui le fait, dites:

label1.Text = "Hello World";

je vais obtenir une exception, et donc je vais devoir l'écrire comme ceci:

Invoke( new MethodInvoker(() =>{label1.Text = "Hello World";}));

c'est, d'après ce que j'ai compris, parce que l'événement OnTimer est déclenché à partir du thread de la minuterie - où le temps est passé jusqu'à ce qu'il ait passé assez pour être au-dessus de L'intervalle, et puis l'événement OnTimer suivant est déclenché. Le .net Timer n'a pas ce problème - dans OnTimer du .net Timer, je peux librement modifier les membres des controls.

Question: Qu'est-ce que je dois changer pour que mon timer s'exécute il est OnTimer event dans le thread principal? Est l'ajout de "Invoquer" le seul choix?

10
demandé sur Istrebitel 2013-10-21 19:02:40

3 réponses

bien qu'il y ait plusieurs façons de procéder, celle que je préférerais généralement est de faire saisir la valeur de SynchronizationContext.Current quand il est créé. Cette valeur contiendra, dans le thread, le contexte de synchronisation courant qui peut être utilisé pour exécuter des méthodes dans la boucle de message dans un contexte D'UI. Cela fonctionnera pour winforms, WPF, silverlight, etc. Tous ces paradigmes établissent un contexte de synchronisation.

il suffit de saisir cette valeur lorsque la minuterie est créé, en supposant qu'il est créé dans le fil UI. Si vous voulez avoir un constructeur/propriété optionnel pour définir la valeur de sorte que vous puissiez l'utiliser même si le timer n'est pas créé dans le thread UI vous pouvez, bien que cela ne devrait pas être nécessaire la plupart du temps.

alors utilisez juste le

public class Timer
{
    private SynchronizationContext syncContext;
    public Timer()
    {
        syncContext = SynchronizationContext.Current;
    }

    public event EventHandler Tick;

    private void OnTick()
    {
        syncContext.Send(state =>
        {
            if (Tick != null)
                Tick(this, EventArgs.Empty);
        }, null);
    }

    //TODO other stuff to actually fire the tick event
}
10
répondu Servy 2013-10-21 15:08:56

il n'y a aucun moyen d'envoyer L'accès de L'élément UI au thread principal. Si la mise à jour D'un élément de L'interface utilisateur est vraiment la seule chose que vous avez l'intention de faire lors de l'appel de minuterie, alors oubliez votre exigence de précision de minuterie. L'utilisateur ne verra pas la différence entre 16 et 50ms.

sinon effectuer le travail de temps critique dans votre appel de minuterie et envoyer le reste du travail D'UI au fil principal:

void OnTimer()
{
  // time critical stuff here

  Invoke( new MethodInvoker(() =>{label1.Text = "Hello World";}));
}
2
répondu Oliver Weichhold 2013-10-21 15:08:53

Dans wpf, vous pouvez utiliser le répartiteur classe à envoyer des messages dans le thread de l'INTERFACE utilisateur:

Dispatcher.CurrentDispatcher.BeginInvoke(
      new Action(()=> label1.Text = "Hello World"));

En winforms vous devez appeler le appeler méthode:

this.Invoke(()=> label1.Text = "Hello World");
2
répondu Alberto 2013-10-21 15:09:37