228 votes

Poursuite de la tâche sur le thread UI

Existe-t-il un moyen "standard" de spécifier qu'une suite de tâche doit être exécutée sur le fil d'exécution à partir duquel la tâche initiale a été créée ?

Actuellement, j'ai le code ci-dessous - il fonctionne mais garder la trace du distributeur et créer une deuxième action semble être une surcharge inutile.

dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
});

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

0 votes

Dans le cas de votre exemple, vous pourriez utiliser Control.Invoke(Action) c'est-à-dire. TextBlock1.Invoke plutôt que dispatcher.Invoke

2 votes

Merci @ColonelPanic, mais j'utilisais WPF (comme indiqué), pas winforms.

372voto

Greg Sansom Points 9059

Appelez la suite avec TaskScheduler.FromCurrentSynchronizationContext() :

    Task UITask= task.ContinueWith(() =>
    {
     this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext());

Cela ne convient que si le contexte d'exécution actuel se trouve sur le thread de l'interface utilisateur.

41 votes

Il n'est valable que si le contexte d'exécution actuel est sur le thread de l'interface utilisateur. Si vous placez ce code à l'intérieur d'une autre tâche, vous obtiendrez une InvalidOperationException (regardez à Exceptions section)

3 votes

Dans .NET 4.5, la réponse de Johan Larsson devrait être utilisée comme moyen standard pour la continuation d'une tâche sur le thread UI. Il suffit d'écrire : await Task.Run(DoLongRunningWork) ; this.TextBlock1.Text = "Complete" ; Voir aussi : blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx

2 votes

Merci de m'avoir sauvé la vie. J'ai passé des heures à comprendre comment appeler les choses du fil principal dans le await/ContinueWith. Pour tous les autres, comment utiliser Google Firebase SDK pour Unity et a toujours les mêmes problèmes, c'est une approche qui fonctionne.

37voto

Johan Larsson Points 4405

Avec l'asynchronisme, il suffit de le faire :

await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);

Cependant :

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.

24voto

Simon_Weaver Points 31141

Si vous avez une valeur de retour que vous devez envoyer à l'interface utilisateur, vous pouvez utiliser la version générique comme ceci :

Dans mon cas, l'appel se fait à partir d'un ViewModel MVVM.

var updateManifest = Task<ShippingManifest>.Run(() =>
    {
        Thread.Sleep(5000);  // prove it's really working!

        // GenerateManifest calls service and returns 'ShippingManifest' object 
        return GenerateManifest();  
    })

    .ContinueWith(manifest =>
    {
        // MVVM property
        this.ShippingManifest = manifest.Result;

        // or if you are not using MVVM...
        // txtShippingManifest.Text = manifest.Result.ToString();    

        System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);

    }, TaskScheduler.FromCurrentSynchronizationContext());

0 votes

Je suppose que le = avant GenerateManifest est une faute de frappe.

0 votes

Si ce code est exécuté en dehors du fil principal, TaskScheduler.FromCurrentSynchronizationContext() lancera une exception. Cela ne vous semble-t-il pas un peu excentrique ?

11voto

Dean Points 161

Je voulais juste ajouter cette version parce que c'est un fil de discussion très utile et je pense que c'est une mise en œuvre très simple. Je l'ai utilisé plusieurs fois dans différents types d'applications multithread :

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });

2 votes

Il ne s'agit pas d'un vote négatif, car cette solution est viable dans certains cas, mais la réponse acceptée est bien meilleure. Elle est agnostique sur le plan technologique ( TaskScheduler fait partie de BCL, Dispatcher ne l'est pas) et peut être utilisé pour composer des chaînes complexes de tâches, car il n'est pas nécessaire de se préoccuper des opérations asynchrones de type "fire-and-forget" (telles que BeginInvoke ).

0 votes

@Kirill pouvez-vous développer un peu, parce que certains fils de discussion SO ont déclaré à l'unanimité que le dispatcher est la méthode correcte si vous utilisez WPF ou WinForms : On peut invoquer une mise à jour de l'interface graphique de manière asynchrone (en utilisant BeginInvoke) ou synchrone (Invoke), bien que l'asynchrone soit généralement utilisé parce qu'on ne voudrait pas bloquer un thread d'arrière-plan juste pour une mise à jour de l'interface graphique. FromCurrentSynchronizationContext ne place-t-il pas la tâche de continuation dans la file d'attente des messages du thread principal de la même manière que le répartiteur ?

0 votes

C'est sûr. Je vais aborder le Dispatcher d'abord. Dispatcher.Invoke/BeginInvoke est un concept WPF ; son homologue dans WinForms serait Control.Invoke/BeginInvoke . Vous devez donc maintenant adapter votre code à la plate-forme spécifique sur laquelle vous travaillez, ce qui n'aurait pas été le cas si vous aviez opté pour l'option TaskScheduler.FromCurrentSynchronizationContext en premier lieu car il fait partie de la bibliothèque des classes de base, et est donc largement disponible.

3voto

Andreas Müller Points 673

Je suis arrivé ici grâce à Google parce que je cherchais un bon moyen de faire des choses sur le thread de l'interface utilisateur après avoir été à l'intérieur d'un appel Task.Run - En utilisant le code suivant vous pouvez utiliser await pour revenir au fil conducteur de l'interface utilisateur.

J'espère que cela aidera quelqu'un.

public static class UI
{
    public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}

public struct DispatcherAwaiter : INotifyCompletion
{
    public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();

    public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);

    public void GetResult() { }

    public DispatcherAwaiter GetAwaiter()
    {
        return this;
    }
}

Utilisation :

... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X