Lorenzo Polidori
Lorenzo Polidori

Reputation: 10512

Waiting for a background thread in C#

What's the difference between:

public class Worker
{
    public static void Execute()
    {
        ExecuteInBackgroundThread().Wait();
    }

    private static Task ExecuteInBackgroundThread()
    {
        return Task.Run(() =>
        {
            // do some long running operation
        });
    }
}

and

public class Worker
{
    public static void Execute()
    {
        ExecuteInBackgroundThread().Wait();
    }

    private static async Task ExecuteInBackgroundThread()
    {
        await Task.Run(() =>
        {
            // do some long running operation
        });
    }
}

I noticed that calling the second version of Worker.Execute() from the UI thread my Windows Phone app gets stuck.

Instead, using the first version everything seems to work fine.

Is the second version of ExecuteInBackgroundThread actually returning a Task<T> that can be awaited? But if that is not the case, should the compiler not give an error saying that we are not returning anything?

Upvotes: 0

Views: 849

Answers (2)

Dirk
Dirk

Reputation: 10958

Let's see what calling Execute for each version does:

The first version calls ExecuteInBackgroundThread which immediately returns a Task. Then Execute blocks until the returned task is finished.

The second version calls ExecuteInBackgroundThread which creates a Task and schedules a continuation which will be executed when the task if finished, and returns another task. Then Execute blocks until the returned task is finished.

The difference is that the second version has a continuation scheduled because of the await keyword. The effect of it depends on the synchronization context of the thread in which you call Execute. When you are on a UI thread the second version will deadlock, because the continuation is scheduled on the UI thread which is blocked because of the call to Wait.

If you are using a console application then there won't be a deadlock because the default synchronization context uses the thread pool to execute the continuations.

Upvotes: 2

i3arnon
i3arnon

Reputation: 116518

First of all, the real reason your app gets stuck is that you have a deadlock. That's because you're using Task.Wait which is blocking the UI thread on a task that is waiting for the UI Thread to complete.

That happens because there's only a single UI thread and so there's a SynchronizationContext that only uses that specific thread.

You can fix your deadlock by using ConfigureAwait(false), which doesn't use the SynchronizationContext:

private static async Task<T> ExecuteInBackgroundThread<T>()
{
    await Task.Run(() =>
    {
        // do some long running operation
    }).ConfigureAwait(false);
}

Other than that, both options are pretty much the same as long as there's no more code in ExecuteInBackgroundThread which is outside of the call to Task.Run. The first option is slightly faster because you're using the task from Task.Run directly. The second option adds another redundant async layer (including a state machine, etc.) on top.

Conclusion: You should use the first option. But don't use Wait which not only blocks your threads, but may result in a deadlock in some cases.

Upvotes: 5

Related Questions