Pav*_*ins 5 .net c# task async-await
我创建了一个简单的.NET Framework 4.7.2 WPF应用程序,其中包含两个控件-文本框和按钮。这是我的代码背后:
private async void StartTest_Click(object sender, RoutedEventArgs e)
{
Output.Clear();
var cancellationTokenSource = new CancellationTokenSource();
// Fire and forget
Task.Run(async () => {
try
{
await Task.Delay(TimeSpan.FromMinutes(1), cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
Task.Delay(TimeSpan.FromSeconds(3)).Wait();
Print("Task delay has been cancelled.");
}
});
await Task.Delay(TimeSpan.FromSeconds(1));
await Task.Run(() =>
{
Print("Before cancellation.");
cancellationTokenSource.Cancel();
Print("After cancellation.");
});
}
private void Print(string message)
{
var threadId = Thread.CurrentThread.ManagedThreadId;
var time = DateTime.Now.ToString("HH:mm:ss.ffff");
Dispatcher.Invoke(() =>
{
Output.AppendText($"{ time } [{ threadId }] { message }\n");
});
}
Run Code Online (Sandbox Code Playgroud)
按下StartTest
按钮后,我在Output
文本框中看到以下结果:
12:05:54.1508 [7] Before cancellation.
12:05:57.2431 [7] Task delay has been cancelled.
12:05:57.2440 [7] After cancellation.
Run Code Online (Sandbox Code Playgroud)
我的问题是,为什么[7] Task delay has been cancelled.
在请求取消令牌的同一线程中执行?
我希望看到的[7] Before cancellation.
,然后[7] After cancellation.
,然后Task delay has been cancelled.
。或至少Task delay has been cancelled.
在另一个线程中执行。
请注意,如果我cancellationTokenSource.Cancel()
从主线程执行,则输出看起来像预期的那样:
12:06:59.5583 [1] Before cancellation.
12:06:59.5603 [1] After cancellation.
12:07:02.5998 [5] Task delay has been cancelled.
Run Code Online (Sandbox Code Playgroud)
更新
有趣的是当我更换
12:05:54.1508 [7] Before cancellation.
12:05:57.2431 [7] Task delay has been cancelled.
12:05:57.2440 [7] After cancellation.
Run Code Online (Sandbox Code Playgroud)
与
12:06:59.5583 [1] Before cancellation.
12:06:59.5603 [1] After cancellation.
12:07:02.5998 [5] Task delay has been cancelled.
Run Code Online (Sandbox Code Playgroud)
.NET使该后台线程保持繁忙状态,并且输出再次如预期的那样:
12:08:15.7259 [5] Before cancellation.
12:08:15.7289 [5] After cancellation.
12:08:18.8418 [7] Task delay has been cancelled..
Run Code Online (Sandbox Code Playgroud)
更新2
我已对代码示例进行了稍许更新,以期使内容更加清晰。
请注意,这不是纯粹的假设问题,而是我花了很多时间在生产代码中理解的一个实际问题。但是为了简洁起见,我创建了这个极其简化的代码示例,以说明相同的行为。
我的问题是,为什么
[7] Task delay has been cancelled.
在请求取消令牌的同一线程中执行?
这是因为await
使用ExecuteSynchronously
flag 调度其任务继续。我也认为这种行为是令人惊讶的,并最初将其报告为错误(按“按设计要求封闭”)。
更具体地说,await
捕获一个上下文,并且如果该上下文与正在完成任务的当前上下文兼容,那么async
继续将直接在正在完成该任务的线程上执行。
要逐步执行:
cancellationTokenSource.Cancel()
。CancellationTokenSource
进入取消状态并运行其回调。Task.Delay
。该回调不是特定于线程的,因此它在线程7上执行。Task
返回的Task.Delay
被取消。在await
已安排的从一个线程池线程延续,线程池中的线程都被认为是互相兼容的,因此,async
继续直接执行上线7。提醒一下,线程池线程仅在有要运行的代码时使用。使用await
to 发送异步代码时Task.Run
,它可以await
在一个线程上运行第一部分(直到),然后在另一个线程上运行另一部分(在之后await
)。
因此,由于线程池线程是可互换的,因此线程7 async
在await
; 之后继续执行该方法不是“错误的” 。这只是一个问题,因为现在继续执行之后的代码Cancel
被阻止了async
。
请注意,如果我从主线程执行cancelTokenSource.Cancel(),则输出将按预期进行
这是因为UI上下文不被认为与线程池上下文兼容。因此,取消Task
返回时Task.Delay
,await
将看到它在UI上下文中,而不是在线程池上下文中,因此它将其延续性排队到线程池中,而不是直接执行。
有趣的是,当我替换
Task.Delay(TimeSpan.FromMinutes(1), cancellationTokenSource.Token)
为cancellationTokenSource.Token.ThrowIfCancellationRequested()
.NET时,该后台线程保持忙碌状态,并且输出再次达到预期
这不是因为线程“忙”。这是因为没有回调了。因此,观察方法是轮询而不是被通知。
该代码设置了一个计时器(通过Task.Delay
),然后将线程返回到线程池。当计时器关闭时,它将从线程池中获取一个线程,并检查取消令牌源是否已取消;如果不是,它将设置另一个计时器并将线程再次返回到线程池。本段的要点是,Task.Run
它不仅仅代表“一个线程”;它在执行代码时仅具有一个线程(即,在中没有await
),并且该线程可以在any之后更改await
。
的一般问题,await
使用ExecuteSynchronously
通常不,除非你是混合阻塞和异步代码的问题。在这种情况下,最好的解决方案是将阻塞代码更改为异步代码。如果您无法做到这一点,则需要注意如何继续在async
之后阻塞的方法await
。这主要是TaskCompletionSource<T>
和的问题CancellationTokenSource
。TaskCompletionSource<T>
有一个很好的RunContinuationsAsynchronously
选项可以覆盖该ExecuteSynchronously
标志;不幸的是,CancellationTokenSource
没有;您必须Cancel
使用来将对线程池的调用排队Task.Run
。
归档时间: |
|
查看次数: |
288 次 |
最近记录: |