236

Is there a 'standard' way to specify that a task continuation should run on the thread from which the initial task was created?

Currently I have the code below - it is working but keeping track of the dispatcher and creating a second Action seems like unnecessary overhead.

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

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});
Greg Sansom
  • 20,442
  • 6
  • 58
  • 76

5 Answers5

399

Call the continuation with TaskScheduler.FromCurrentSynchronizationContext():

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

This is suitable only if the current execution context is on the UI thread.

Greg Sansom
  • 20,442
  • 6
  • 58
  • 76
  • 43
    Its valid only if the current execution context is on the UI thread. If you put this code inside another Task, then you get InvalidOperationException (look at [Exceptions](http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.fromcurrentsynchronizationcontext(v=vs.110).aspx) section) – stukselbax Jun 19 '14 at 08:41
  • 3
    In .NET 4.5 Johan Larsson's answer should be used as standard way for a task continuation on the UI thread. Just write: await Task.Run(DoLongRunningWork); this.TextBlock1.Text = "Complete"; See also: http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx – Marcel Wolterbeek Jul 15 '16 at 09:32
  • 2
    Thx for saving my life. I spend hours to figure out how to call main thread things within the await/ContinueWith. For everyone else how is using [Google Firebase SDK for Unity](https://firebase.google.com/docs/unity/setup) and still has the same issues, this is a working approach. – C-H-a-P Sep 15 '19 at 10:22
  • 2
    @MarcelW - `await` is a good pattern - but only if you are inside an `async` context (such as a method declared `async`). If not, it is still necessary to do something like this answer. – ToolmakerSteve Nov 24 '19 at 00:18
  • @MarcelW Your link is dead and there's no cache of this page on the Wayback Machine. Can you find another source? – Jon Bangsberg Nov 12 '21 at 19:05
  • @JonBangsberg You can look at the [ConfigureAwait FAQ by Stephen Toub](https://devblogs.microsoft.com/dotnet/configureawait-faq/#can-i-use-task-run-to-avoid-using-configureawaitfalse). There is a lot of interesting information about async. – Marcel Wolterbeek Nov 14 '21 at 13:03
  • This allows to continue Task within Unity3D context, and that's very helpful. Was using `TaskContinuationOptions.ExecuteSynchronously` but didn't work as expected. – StackHola Sep 05 '22 at 09:50
  • If you use `await` you are guaranteed to return on a UI thread if you started there, but not if you didn't. In other words you don't have to worry. It sounds like this is not the equivalent, since it only works when starting on the UI thread. So what is the equivalent of `await` but using `ContinueWith`? – Emperor Eto Sep 30 '22 at 19:16
40

With async you just do:

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);

However:

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.
Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
  • 2
    The comment under the `false` version confuses me. I thought `false` means it might continue on a *different* thread. – ToolmakerSteve Jun 27 '18 at 07:25
  • 1
    @ToolmakerSteve Depends which thread you're thinking of. The worker thread used by Task.Run, or the caller thread? Remember, "same thread the task finished on" means the worker thread (avoiding 'switching' between threads). Also, ConfigureAwait(true) doesn't guarantee that control returns to the same *thread*, only to the same *context* (though the distinction may not be significant). – Max Barraclough Nov 12 '18 at 16:39
  • @MaxBarraclough - Thanks, I misread which "same thread" was meant. *avoiding switching between threads* in the sense of maximizing performance by using whatever thread happens to be running [to perform the "do some stuff" task], that clarifies it for me. – ToolmakerSteve Nov 12 '18 at 17:09
  • 2
    The question doesn't specify being inside of an `async` method (which is necessary, to use `await`). What is the answer when `await` is not available? – ToolmakerSteve Nov 23 '19 at 23:27
25

If you have a return value you need to send to the UI you can use the generic version like this:

This is being called from an MVVM ViewModel in my case.

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());
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • I'm guessing the = before GenerateManifest is a typo. – Sebastien F. Oct 19 '15 at 13:10
  • 1
    If this code executes 'off' the main thread, `TaskScheduler.FromCurrentSynchronizationContext()` will throw an exception. Does this not seem a bit flakey? – maxp Nov 26 '21 at 15:01
11

I just wanted to add this version because this is such a useful thread and I think this is a very simple implementation. I have used this multiple times in various types if multithreaded application:

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });
Dean
  • 731
  • 8
  • 21
  • 2
    Not downvoting since this is a viable solution in some scenarios; however, the accepted answer is way better. It is technology-agnostic (`TaskScheduler` is part of BCL, `Dispatcher` is not) and can be used to compose complex chains of tasks due to not having to worry about any fire-and-forget async operations (such as `BeginInvoke`). – Kirill Shlenskiy Sep 22 '14 at 04:01
  • @Kirill can you expand a little, because some SO threads have unanimously declared the dispatcher to be the correct method if using WPF of WinForms: One can invoke a GUI update either asynchronously (using BeginInvoke) or synchronously (Invoke), though typically the async is used because one would not want to block a background thread just for a GUI update. Does FromCurrentSynchronizationContext not put the continuation task into the main thread message queue in just the same way as the dispatcher? – Dean Sep 22 '14 at 04:37
  • sure thing. I'll address the `Dispatcher` issue first. `Dispatcher.Invoke/BeginInvoke` is a WPF concept; its counterpart in WinForms would be `Control.Invoke/BeginInvoke`. So now you have to tailor your code to the specific platform you're working with - which wouldn't be the case if you went with `TaskScheduler.FromCurrentSynchronizationContext` in the first place as it is part of the Base Class Library, and is therefore widely available. – Kirill Shlenskiy Sep 22 '14 at 04:45
  • 1
    Right, but the OP is certainly asking about WPF (and tagged it so), and does not want to keep a reference to any dispatcher (and I assume any synchronization context either - you can only get this from the main thread and you have to store a reference to it somewhere). Which is why I like the solution that I posted: there is a thread-safe static reference built in that requires none of this. I think this is extremely useful in the WPF context. – Dean Sep 22 '14 at 04:56
  • 3
    Just wanted to reinforce my last comment: The developer not only has to store the sync context, but he/she has to know that this is only available from the main thread; this problem has been the cause of confusion in dozens of SO questions: People all the time try to get that from the the worker thread. If their code has itself been moved into a worker thread, it fails because of this issue. So because of the prevalence of WPF, this should definitely be clarified here in this popular question. – Dean Sep 22 '14 at 05:23
  • The portable solution is just as simple as the non-portable one (some might argue that passing around a `TaskScheduler` reference is a chore - others would rebut that it is not always necessary, and in simple scenarios there is actually less typing involved), so it wins every time simply because you get more bang for your buck. – Kirill Shlenskiy Sep 22 '14 at 05:25
  • Well, the accepted answer is more than double the amount of code: You have to plug it into the code from the question. – Dean Sep 22 '14 at 05:30
  • FWIW, The accepted answer would be 5 characters shorter than this answer, and typically be formatted to the same 6 lines as this, if didn't declare the intermediate task: `Task.Factory.StartNew( () => { DoLongRunningWork(); } ).ContinueWith( () => { this.TextBlock1.Text = "Complete"; }, TaskScheduler.FromCurrentSynchronizationContext() );` – ToolmakerSteve Jun 27 '18 at 07:39
  • 1
    ... nevertheless, Dean's observation about [the accepted answer] needing to keep track of sync context if code might not be on main thread is important to note, and avoiding that is a benefit of this answer. – ToolmakerSteve Jun 27 '18 at 07:47
  • I was just about to down vote this also but based on the observation discussed here I will leave it hoping that others will up-vote the accepted answer way more than this one - both answers address the issue of the question but the accepted answer is just way better in terms of re-usability and maintainance... – user8276908 Apr 28 '19 at 11:56
4

Got here through google because i was looking for a good way to do things on the ui thread after being inside a Task.Run call - Using the following code you can use await to get back to the UI Thread again.

I hope this helps someone.

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;
    }
}

Usage:

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

Dbl
  • 5,634
  • 3
  • 41
  • 66