Réponses

When an asynchronous method resumes after an asynchronous await, it will attempt to resume in the same "context." So when your testAsync method awaits Task.Run, it captures the context before doing the await... and when the Task.Run completes, testAsync
will resume in that captured context. When testAsync reaches the end of its method, then the task returned by testAsync will complete.

Your button1 handler (I assume it's a click handler) is calling Task.Result, which waits for the asynchronous task to complete. The problem is that it is doing this blocking on the UI thread, and testAsync is trying to resume on the UI thread context. So
you get a deadlock.

Here's what's happening, step by step:

button1() calls testAsync().

testAsync calls Task.Run; the delegate is queued to the thread pool, and the task returned by Task.Run will be completed when the delegate completes.

testAsync awaits the task returned by Task.Run. Since it's not complete yet, testAsync returns an uncompleted task to its caller. The task returned by testAsync will be completed when testAsync completes.

button1 receives the uncompleted task from testAsync (storing it into "temp").

button1 calls Result on the uncompleted task from testAsync. This causes button1 to block (synchronously).

The thread pool delegate completes. This causes the task returned by Task.Run to complete.

testAsync attempts to resume execution so that it can complete (executing its "return" statement). However, its captured context is a UI context, and the UI thread is blocked. So it waits for the UI thread to be available, while the UI thread waits for
testAsync to complete.

One Solution

The best way (IMO) to solve this problem is to change your event handler to be asynchronous:

However, there is a drawback to this solution: you need to think about reentrancy. e.g., what happens if the user clicks the button while the asynchronous handler is still running? Sometimes this is OK, but often you need to disable your button at the beginning
of the handler and re-enable it at the end:

When an asynchronous method resumes after an asynchronous await, it will attempt to resume in the same "context." So when your testAsync method awaits Task.Run, it captures the context before doing the await... and when the Task.Run completes, testAsync
will resume in that captured context. When testAsync reaches the end of its method, then the task returned by testAsync will complete.

Your button1 handler (I assume it's a click handler) is calling Task.Result, which waits for the asynchronous task to complete. The problem is that it is doing this blocking on the UI thread, and testAsync is trying to resume on the UI thread context. So
you get a deadlock.

Here's what's happening, step by step:

button1() calls testAsync().

testAsync calls Task.Run; the delegate is queued to the thread pool, and the task returned by Task.Run will be completed when the delegate completes.

testAsync awaits the task returned by Task.Run. Since it's not complete yet, testAsync returns an uncompleted task to its caller. The task returned by testAsync will be completed when testAsync completes.

button1 receives the uncompleted task from testAsync (storing it into "temp").

button1 calls Result on the uncompleted task from testAsync. This causes button1 to block (synchronously).

The thread pool delegate completes. This causes the task returned by Task.Run to complete.

testAsync attempts to resume execution so that it can complete (executing its "return" statement). However, its captured context is a UI context, and the UI thread is blocked. So it waits for the UI thread to be available, while the UI thread waits for
testAsync to complete.

One Solution

The best way (IMO) to solve this problem is to change your event handler to be asynchronous:

However, there is a drawback to this solution: you need to think about reentrancy. e.g., what happens if the user clicks the button while the asynchronous handler is still running? Sometimes this is OK, but often you need to disable your button at the beginning
of the handler and re-enable it at the end:

The reason deadlock doesn't occur on a worker thread is because of the "context".

As I cover in my
intro post, the "context" is usually one of three things:

If you're on a UI thread, then it's a UI context.

If you're responding to an ASP.NET request, then it's an ASP.NET request context.

Otherwise, it's usually a thread pool context.

[The actual rules are more complex; the "context" is really the current SynchronizationContext unless it is null, in which case it is the current TaskScheduler. In most async code written today, the simple 3-point answer above is sufficient;
but if you're doing complex things with async, then you need to know the actual rules.]

So the original example (the one that causes a deadlock), the testAsync method attempts to resume on its captured context - a UI context.

In the new example, the Task4 method runs on a thread pool thread, so it captures a thread pool context instead of a UI context. In the thread pool context,
any thread pool thread may be used to continue the asynchronous method. So when t1 completes and Task4 resumes on the thread pool context, it just grabs another thread pool thread to complete the task returned by Task4.

The central point is this: the async "context" that is captured is not
for a specific thread. The UI context will synchronize back to the originating UI thread, but other contexts won't. In particular, the thread pool context won't.