【问题标题】:To Task.Run or not to Task.Run到 Task.Run 还是不到 Task.Run
【发布时间】:2015-05-09 21:06:48
【问题描述】:

假设我有一个包含异步方法的接口,并且该接口有两个不同的实现。这两种实现中的一种自然是异步的,而另一种则不是。实现非异步方法的“最正确”方式是什么?

public interface ISomething {
    Task<Foo> DoSomethingAsync();
}

// Normal async implementation
public class Implementation1 : ISomething {
    async Task<Foo> ISomething.DoSomethingAsync() {
        return await DoSomethingElseAsync();
    }
}

// Non-async implementation
public class Implementation2 : ISomething {
    // Should it be:
    async Task<Foo> ISomething.DoSomethingAsync() {
        return await Task.Run(() => DoSomethingElse());
    }
    // Or:
    async Task<Foo> ISomething.DoSomethingAsync() {
        return DoSomethingElse();
    }
}

我尝试跟上 Stephen Cleary 的 blog,我知道这些都没有真正提供任何异步好处,我对此表示满意。第二个对我来说似乎更正确,因为它不会假装它不是,但它确实会发出编译器警告,并且这些加起来会让人分心。

这一切都在 ASP.NET 中(Web MVC 和 WebAPI),如果有区别的话。

【问题讨论】:

  • 您的“正常异步实现”无效...您没有返回任何内容,但您的返回类型是 Task&lt;Foo&gt;
  • @B.K.谢谢,我忘记了那里的return。固定。
  • 您也可以使用Task.RunSynchronously 代替Task.Run 来获得async 样式的异常传播行为,而无需冗余ThreadPool 卸载,如here 所述。
  • @Noseratio 谢谢,很高兴知道。

标签: c# .net asynchronous task-parallel-library async-await


【解决方案1】:

您可以完全放弃async 修饰符并使用Task.FromResult 同步返回已完成的任务:

Task<Foo> ISomething.DoSomethingAsync()
{
    return Task.FromResult(DoSomethingElse());
}

这会处理警告并具有更好的性能,因为它不需要async 方法的状态机开销。

但是,这对change the semantics of exception handling 有点作用。如果这是一个问题,那么您应该使用同步 async 方法并接受警告(或通过评论将其关闭):

#pragma warning disable 1998
    async Task<Foo> ISomething.DoSomethingAsync() 
#pragma warning restore 1998
    {
        return DoSomethingElse();
    }

正如 Stephen Cleary 所建议的,您还可以通过等待已完成的任务来处理该警告(同时保持方法同步):

async Task<Foo> ISomething.DoSomethingAsync() 
{
    await Task.FromResult(false); // or Task.CompletedTask in .Net 4.6
    return DoSomethingElse();
}

【讨论】:

  • 或异步 noop:await Task.CompletedTask; in .NET 4.6。
  • @StephenCleary 摆脱警告?这不是很牛逼吗?
  • 酷,谢谢。我将使用await 调用它,所以我并没有真正考虑过不将async 放在该方法上。我会考虑这两种方法 - 听起来任何一种都适合我。
  • @i3arnon:是的,它很老套。但我也不是#pragma 的忠实粉丝。
  • @Noseratio:我会提防RunSynchronously(Default);在这种情况下,它可能会将工作排队到后台线程,我不确定。
【解决方案2】:

这真的取决于你的方法在做什么:

  • 没有 I/O,CPU 工作量可忽略不计
  • CPU 密集型工作
  • I/O 密集型工作

无 I/O,CPU 工作量可忽略不计

您应该同步计算结果并创建一个保存结果的任务。

Task<Foo> ISomething.DoSomethingAsync() {
    Foo result;
    // do something not hurting performance
    // no I/O here
    return Task.FromResult(result);
}

但是请注意,任何异常都将在调用方法时引发,而不是在等待任务时引发。对于符合其他工作类型的后一种情况,您仍然应该使用 async:

async Task<Foo> ISomething.DoSomethingAsync() {
    Foo result;
    // do something not hurting performance
    // no I/O here
    return result;
}

CPU 密集型工作

您应该使用Task.Run 启动一个任务,并在任务中执行 CPU 密集型工作。

Task<Foo> ISomething.DoSomethingAsync() {
    return Task.Run(() => {
        Foo result;
        // do some CPU intensive work here
        return result;
    });
}

I/O 密集型工作

您应该使用 async 关键字并等待任何异步 I/O 方法。不要使用同步 I/O 方法。

async Task<Foo> ISomething.DoSomethingAsync() {
    Stream s = new ....

    // or any other async I/O operation
    var data = await s.ReadToEndAsync(); 

    return new Foo(data);
}

【讨论】:

  • Task.Run 并没有真正增加任何价值。卸载到ThreadPool 线程的决定应该留给消费者。
  • 另外,由于这是一个 ASP.NET 方案,Task.Run 删除值。
猜你喜欢
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
相关资源
最近更新 更多