Run "async" method on a background thread

McGarnagle picture McGarnagle · Nov 30, 2013 · Viewed 51.5k times · Source

I'm trying to run an "async" method from an ordinary method:

public string Prop
{
    get { return _prop; }
    set
    {
        _prop = value;
        RaisePropertyChanged();
    }
}

private async Task<string> GetSomething()
{
    return await new Task<string>( () => {
        Thread.Sleep(2000);
        return "hello world";
    });
}

public void Activate()
{
    GetSomething.ContinueWith(task => Prop = task.Result).Start();
    // ^ exception here
}

The exception thrown is:

Start may not be called on a continuation task.

What does that mean, anyway? How can I simply run my async method on a background thread, dispatch the result back to the UI thread?

Edit

Also tried Task.Wait, but the waiting never ends:

public void Activate()
{
    Task.Factory.StartNew<string>( () => {
        var task = GetSomething();
        task.Wait();

        // ^ stuck here

        return task.Result;
    }).ContinueWith(task => {
        Prop = task.Result;
    }, TaskScheduler.FromCurrentSynchronizationContext());
    GetSomething.ContinueWith(task => Prop = task.Result).Start();
}

Answer

Cory Nelson picture Cory Nelson · Nov 30, 2013

To fix your example specifically:

public void Activate()
{
    Task.Factory.StartNew(() =>
    {
        //executes in thread pool.
        return GetSomething(); // returns a Task.
    }) // returns a Task<Task>.
    .Unwrap() // "unwraps" the outer task, returning a proxy
              // for the inner one returned by GetSomething().
    .ContinueWith(task =>
    {
        // executes in UI thread.
        Prop = task.Result;
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

This will work, but it's old-school.

The modern way to run something on a background thread and dispatch back to UI thread is to use Task.Run(), async, and await:

async void Activate()
{
    Prop = await Task.Run(() => GetSomething());
}

Task.Run will start something in a thread pool thread. When you await something, it automatically comes back in on the execution context which started it. In this case, your UI thread.

You should generally never need to call Start(). Prefer async methods, Task.Run, and Task.Factory.StartNew -- all of which start the tasks automatically. Continuations created with await or ContinueWith are also started automatically when their parent completes.