与Task.Result
或await
相比,C#5中的Task
await
会阻止执行等待的线程处于休眠状态。相反,使用async
关键字的方法需要为await
,以便async
的调用只会使方法返回表示执行await
的新任务。方法
但是当Task
'ed async
在await
方法再次收到CPU时间之前完成时,Task
会将async
识别为已完成,因此Task
方法仅在以后返回await
对象。在某些情况下,这可能比可接受的晚,因为开发人员认为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
的框架,第一个结构应该是通用的。因此YieldAwaitable
应该返回Awaitable,它总是会产生Task.Yield()
返回的Task.WhenAll(IEnumerable<Task> tasks)
。
不幸的是,Task
等任务并行库提供的大多数功能仅在getSthAsync
上运行,因此Task
的结果应为Task
。
那么是否可以返回总是产生的{{1}}?
答案 0 :(得分:6)
首先,异步方法的使用者不应该假设它会“屈服”,因为它与异步无关。如果消费者需要确保卸载到另一个线程,他们应该使用Task.Run
来强制执行该操作。
其次,我没有看到使用Task.Run
或Task.Yield
是如何有问题的,因为它在async方法中使用,该方法返回Task
而不是{{1} }。
如果您想创建一个行为类似于YieldAwaitable
的{{1}},您可以在异步方法中使用Task
:
YieldAwaitable
编辑:
正如评论中所提到的,这种竞争条件可能并不总是屈服。这种竞争条件与Task.Yield
和async Task Yield()
{
await Task.Yield();
}
的实施方式密切相关。为避免这种情况,您可以创建自己的Task
和TaskAwaiter
:
Task
这将创建始终产生的任务,因为TaskAwaiter
始终返回false。它可以像这样使用:
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();
}
注意:我强烈反对任何人实际做这类事情。
答案 1 :(得分:0)
这是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
变量中,则将获得默认的任务行为。对于我的用例来说,这是一个很大的缺点,但是我看不到任何解决方案。