首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >如何创建一个总是产生结果的任务?

如何创建一个总是产生结果的任务?
EN

Stack Overflow用户
提问于 2015-08-22 09:36:38
回答 2查看 716关注 0票数 4

Task.Wait()Task.Result不同,在C# 5中对Task进行await‘可以防止执行等待的线程处于空闲状态。相反,使用await关键字的方法需要是async,这样await的调用只会使方法返回一个新任务,该任务表示async方法的执行。

但是,当await‘ed Taskasync方法再次收到CPU时间之前完成时,await会识别出Task已完成,因此async方法将只在稍后时间返回Task对象。在某些情况下,这可能会晚于可接受的时间,因为开发人员假定await‘ing总是在async方法中延迟后续语句,这可能是一个常见的错误。

错误的async方法的结构可能如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async Task doSthAsync()
{
    var a = await getSthAsync();

    // perform a long operation
}

然后,有时doSthAsync()只会在很长一段时间后才返回Task

我知道它应该写成这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async Task doSthAsync()
{
    var a = await getSthAsync();

    await Task.Run(() =>
    {
        // perform a long operation
    };
}

..。或者说:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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是可能的吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-08-22 09:46:11

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

其次,我不认为使用Task.RunTask.Yield是有问题的,因为它是在返回Task而不是YieldAwaitable的异步方法中使用的。

如果您想要创建一个行为类似于TaskYieldAwaitable,只需在异步方法中使用Task.Yield

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
async Task Yield()
{
    await Task.Yield();
}

编辑:

正如在评论中提到的,这有一个种族条件,它可能并不总是屈服。这种竞争条件是如何实现TaskTaskAwaiter的固有条件。为了避免这种情况,您可以创建自己的TaskTaskAwaiter

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。它可以这样使用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static readonly YieldTask YieldTask = new YieldTask();

private static async Task MainAsync()
{
    await YieldTask;
    // something
}

注意:我强烈劝阻任何人不要真正做这种事。

票数 7
EN

Stack Overflow用户

发布于 2020-05-18 09:37:37

以下是i3arnon's YieldTask的精良版本

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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变量,则会得到默认的任务行为。这对于我的用例来说是一个相当大的缺点,但我看不到它的任何解决方案。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32158950

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文