与Task.Wait()
或Task.Result
不同,在C# 5中对Task
进行await
‘可以防止执行等待的线程处于空闲状态。相反,使用await
关键字的方法需要是async
,这样await
的调用只会使方法返回一个新任务,该任务表示async
方法的执行。
但是,当await
‘ed Task
在async
方法再次收到CPU时间之前完成时,await
会识别出Task
已完成,因此async
方法将只在稍后时间返回Task
对象。在某些情况下,这可能会晚于可接受的时间,因为开发人员假定await
‘ing总是在async
方法中延迟后续语句,这可能是一个常见的错误。
错误的async
方法的结构可能如下所示:
async Task doSthAsync()
{
var a = await getSthAsync();
// perform a long operation
}
然后,有时doSthAsync()
只会在很长一段时间后才返回Task
。
我知道它应该写成这样:
async Task doSthAsync()
{
var a = await getSthAsync();
await Task.Run(() =>
{
// perform a long operation
};
}
..。或者说:
async Task doSthAsync()
{
var a = await getSthAsync();
await Task.Yield();
// perform a long operation
}
但我不认为最后两个模式漂亮,并希望防止错误的发生。我正在开发一个提供getSthAsync
的框架,第一个结构应该是通用的。因此,getSthAsync
应该返回一个Awaitable,它总是像Task.Yield()
返回的YieldAwaitable
那样产生结果。
不幸的是,任务并行库( Task )提供的大多数特性,如Task.WhenAll(IEnumerable<Task> tasks)
,只对Task
进行操作,因此getSthAsync
的结果应该是一个Task
。
那么,返回一个总是产生结果的Task
是可能的吗?
发布于 2015-08-22 09:46:11
首先,异步方法的使用者不应该假设它会“屈服”,因为这与异步无关。如果使用者需要确保有一个卸载到另一个线程,他们应该使用Task.Run
来强制执行。
其次,我不认为使用Task.Run
或Task.Yield
是有问题的,因为它是在返回Task
而不是YieldAwaitable
的异步方法中使用的。
如果您想要创建一个行为类似于Task
的YieldAwaitable
,只需在异步方法中使用Task.Yield
:
async Task Yield()
{
await Task.Yield();
}
编辑:
正如在评论中提到的,这有一个种族条件,它可能并不总是屈服。这种竞争条件是如何实现Task
和TaskAwaiter
的固有条件。为了避免这种情况,您可以创建自己的Task
和TaskAwaiter
。
public class YieldTask : Task
{
public YieldTask() : base(() => {})
{
Start(TaskScheduler.Default);
}
public new TaskAwaiterWrapper GetAwaiter() => new TaskAwaiterWrapper(base.GetAwaiter());
}
public struct TaskAwaiterWrapper : INotifyCompletion
{
private TaskAwaiter _taskAwaiter;
public TaskAwaiterWrapper(TaskAwaiter taskAwaiter)
{
_taskAwaiter = taskAwaiter;
}
public bool IsCompleted => false;
public void OnCompleted(Action continuation) => _taskAwaiter.OnCompleted(continuation);
public void GetResult() => _taskAwaiter.GetResult();
}
这将创建总是生成的任务,因为IsCompleted
总是返回false。它可以这样使用:
public static readonly YieldTask YieldTask = new YieldTask();
private static async Task MainAsync()
{
await YieldTask;
// something
}
注意:我强烈劝阻任何人不要真正做这种事。
发布于 2020-05-18 09:37:37
以下是i3arnon's YieldTask
的精良版本
public class YieldTask : Task
{
public YieldTask() : base(() => { },
TaskCreationOptions.RunContinuationsAsynchronously)
=> RunSynchronously();
public new YieldAwaitable.YieldAwaiter GetAwaiter()
=> default;
public new YieldAwaitable ConfigureAwait(bool continueOnCapturedContext)
{
if (!continueOnCapturedContext) throw new NotSupportedException();
return default;
}
}
YieldTask
在创建时立即完成,但是它的侍者却不这么说。GetAwaiter().IsCompleted
总是返回false
。这种恶作剧使await
操作符在每次等待此任务时都会触发所需的异步交换机。实际上,创建多个YieldTask
实例是多余的。单身人士也同样有效。
不过,这种方法存在一个问题。Task
类的底层方法不是虚拟的,使用new
修饰符隐藏它们意味着多态不起作用。如果将YieldTask
实例存储到Task
变量,则会得到默认的任务行为。这对于我的用例来说是一个相当大的缺点,但我看不到它的任何解决方案。
https://stackoverflow.com/questions/32158950
复制相似问题