using (var ts = new TransactionScope()) {
// ...
Foo();
// ...
ts.Complete();
}
async void Foo() {
// ... тут мы находимся в контексте транзакции
if (Transaction.Current != null) await Task.Yield();
// ... а тут его уже нет!
}
第三,这个构造可以清除调用堆栈。如果程序在处理一堆嵌套延续时因堆栈溢出而崩溃,这将很有用。
例如,考虑一个简化的实现AsyncLock:
class AsyncLock
{
private Task unlockedTask = Task.CompletedTask;
public async Task<Action> Lock()
{
var tcs = new TaskCompletionSource<object>();
await Interlocked.Exchange(ref unlockedTask, tcs.Task);
return () => tcs.SetResult(null);
}
}
在这里,传入的锁请求在延续时隐式排队。看起来会出什么问题?
private static async Task Foo()
{
var _lock = new AsyncLock();
var unlock = await _lock.Lock();
for (var i = 0; i < 100000; i++) Bar(_lock);
unlock();
}
private static async void Bar(AsyncLock _lock)
{
var unlock = await _lock.Lock();
// do something sync
unlock();
}
CancellationTokenSource cts;
void Start()
{
cts = new CancellationTokenSource();
// Запускаем асинхронную операцию
var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
// Ждем окончания
// После окончания операции обрабатываем результат/отмену/исключения
}
async Task<int> SomeWork(CancellationToken cancellationToken)
{
int result = 0;
bool loopAgain = true;
while (loopAgain)
{
// Что-то делаем ...
loopAgain = /* проверка на окончание цикла && */ cancellationToken.IsCancellationRequested;
if (loopAgain) {
// переотправляет задачу в очередь пула потоков чтобы другие задачи, которые ожидали выполнения смогли использовать данный поток
await Task.Yield();
}
}
cancellationToken.ThrowIfCancellationRequested();
return result;
}
void Cancel()
{
// Запрашиваем отмену операции
cts.Cancel();
}
此方法返回一个旨在传递给 operator 的特殊值
await
,并且与此运算符隔离是没有意义的。该构造
await Task.Yield()
做一件相当简单的事情——它中断当前方法并立即在当前同步上下文中安排它的继续。这种设计用于不同的目的。
首先,此构造可用于立即将控制权返回给调用代码。例如,当从事件处理程序调用时,事件将被视为已处理:
其次,此构造用于清理同步调用上下文。例如,这是您可以“关闭”当前事务(环境事务)的方式:
第三,这个构造可以清除调用堆栈。如果程序在处理一堆嵌套延续时因堆栈溢出而崩溃,这将很有用。
例如,考虑一个简化的实现
AsyncLock
:在这里,传入的锁请求在延续时隐式排队。看起来会出什么问题?
在这里,该方法的延续
Bar
在另一个方法调用 的同时Bar
被调用unlock()
。事实证明,方法Bar
和委托之间存在间接递归unlock
,这会迅速吞噬堆栈并导致其溢出。添加一个调用
Task.Yield()
会将执行转移到一个“干净”的堆栈帧,错误将消失:顺便说一下,修复上面代码的另一种方法是使用标志
RunContinuationsAsynchronously
:第四,当在 UI 线程上使用时,这种设计允许您处理累积的 I/O 事件,这对于长期的 UI 更新很有用。
例如,当向表中添加一百万行时,程序将在添加所有行之前不响应用户操作。但是,如果,例如,在每千行添加之后,
await Task.Yield()
插入一个调用,程序将能够处理用户操作,并且看起来不会被冻结。在WinForms中,一个方法可以用于相同的目的
Application.DoEvents()
- 但它的过度使用会导致堆栈溢出。await Task.Yield()
是可以在WinForms和WPF中使用的通用方式。我认为这里没有人回答为什么需要 Task.Yield 的问题。当一个任务(Task)使用无限循环(一般来说,任何长时间的同步工作)并且可以从池中保留一个线程只为它自己使用并防止其他任务使用这个线程时需要它。Task.Yield 将任务重新提交到线程池队列中,其他一直在等待执行的任务就可以使用这个挂起的线程。
例子: