如何创建一个始终产生的任务?

时间:2015-08-22 17:36:38

标签: c# .net multithreading asynchronous async-await

Task.Resultawait相比,C#5中的Task await会阻止执行等待的线程处于休眠状态。相反,使用async关键字的方法需要为await,以便async的调用只会使方法返回表示执行await的新任务。方法

但是当Task'ed asyncawait方法再次收到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}}?

2 个答案:

答案 0 :(得分:6)

首先,异步方法的使用者不应该假设它会“屈服”,因为它与异步无关。如果消费者需要确保卸载到另一个线程,他们应该使用Task.Run来强制执行该操作。

其次,我没有看到使用Task.RunTask.Yield是如何有问题的,因为它在async方法中使用,该方法返回Task而不是{{1} }。

如果您想创建一个行为类似于YieldAwaitable的{​​{1}},您可以在异步方法中使用Task

YieldAwaitable

编辑:

正如评论中所提到的,这种竞争条件可能并不总是屈服。这种竞争条件与Task.Yieldasync Task Yield() { await Task.Yield(); } 的实施方式密切相关。为避免这种情况,您可以创建自己的TaskTaskAwaiter

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变量中,则将获得默认的任务行为。对于我的用例来说,这是一个很大的缺点,但是我看不到任何解决方案。