C# 正在使用任务。运行错误的做法?

C# 正在使用任务。运行错误的做法?,c#,asynchronous,async-await,C#,Asynchronous,Async Await,通常,在类上实现异步方法时,我会编写如下内容: public Task<Guid> GetMyObjectIdAsync(string objectName) { return Task.Run(() => GetMyObjectId(objectName)); } private Guid GetMyObjectId(string objectName) { using (var unitOfWork = _myUnitOfWorkFactory.Creat

通常,在类上实现异步方法时,我会编写如下内容:

public Task<Guid> GetMyObjectIdAsync(string objectName)
{
    return Task.Run(() => GetMyObjectId(objectName));
}

private Guid GetMyObjectId(string objectName)
{
    using (var unitOfWork = _myUnitOfWorkFactory.CreateUnitOfWork())
    {
        var myObject = unitOfWork.MyObjects.Single(o => o.Name == objectName);
        return myObject.Id;
    }
}
公共任务GetMyObjectIdAsync(字符串objectName) { 返回Task.Run(()=>GetMyObjectId(objectName)); } 私有Guid GetMyObjectId(字符串objectName) { 使用(var unitOfWork=\u myUnitOfWorkFactory.CreateUnitOfWork()) { var myObject=unitOfWork.MyObjects.Single(o=>o.Name==objectName); 返回myObject.Id; } } 这种模式允许我根据情况同步和异步使用相同的逻辑(我的大部分工作都在旧代码库中,还没有很多支持异步调用),因为我可以公开同步方法,并在需要时获得最大的兼容性

最近我读了几篇SO帖子,建议使用
Task.Run()
是个坏主意,应该只在某些情况下使用,但这些情况似乎不是很清楚


我上面描述的模式实际上是个坏主意吗?这样做是否会失去异步调用的某些功能/预期目的?或者这是一个合法的实现?

您所做的是将同步操作卸载到另一个线程。如果你的线是“特别的”,那就很好了。“特殊”线程的一个示例是UI线程。在这种情况下,您可能希望卸载它的工作,以保持UI的响应性(另一个例子是某种侦听器)

然而,在大多数情况下,您只是将工作从一个线程转移到另一个线程。这不会增加任何价值,也会增加不必要的开销

因此:

我上面描述的模式实际上是个坏主意吗

是的。将同步工作卸载到
线程池中,并假装它是异步的,这是一个坏主意

这样做是否会失去异步调用的某些功能/预期目的

实际上,这个操作一开始就没有异步。如果您在远程计算机上执行此操作,并且可以从异步执行中获益,则操作本身需要真正异步,这意味着:

var myObject = await unitOfWork.MyObjects.SingleAsync(o => o.Name == objectName);
您当前正在做的是“异步过同步”,您可能不应该这样做。更多

如果我有理由需要在任务中完成此任务,而我正处于这样的情况下,
task.Run()
确实有意义(我有理由将其卸载到另一个线程上),那么最好的方法是将整个任务打包:

Task task = Task.Run(() => _resource = GetResourceForID(GetMyObjectIdAsync(SomeLongRunningWayToGetName())));
这里的
Task.Run()
对于作为调用方的我来说可能是个坏主意,或者可能是个好主意,因为我确实从它给我的东西中获益

然而,如果我看到你的签名,我会认为用你的代码实现这一点的最好方法是将它转换成使用该方法的代码

Task task = SomeLongRunningWayToGetName()
  .ContinueWith(t => GetMyObjectIdAsync(t.Result))
  .ContinueWith(t => _resource = GetResourceForIDAsync(t.Result));
(或使用
async
wait
进行类似操作)

充其量这对
Task.Run()
的分块效果较差。更糟糕的是,我在等待这一点,只是为了从更好的异步性中获益,而这种异步性在一个可以利用它(如果它真的存在)的上下文中是无法提供的。(例如,我可能已经在一个MVC操作中使用了它,我已经使其异步化,因为我认为额外的开销会在更好的线程池使用中得到补偿)

因此,虽然
Task.Run()
有时很有用,但在这种情况下总是不好的。如果您不能为我提供比我自己能够使用该类更大的异步性,请不要让我相信您是这样做的

只有在确实调用异步I/O时,才提供public
XXXAsync()
方法

如果确实需要删除异步方法,例如匹配共享基或接口的签名,则最好是:

public Task<Guid> GetMyObjectIdAsync(string objectName)
{
  return Task.FromResult(GetMyObjectId(objectName);
}
公共任务GetMyObjectIdAsync(字符串objectName) { 返回Task.FromResult(GetMyObjectId(objectName); }
这也很糟糕(调用者最好还是直接调用
GetMyObjectId()
),但至少如果代码
wait
s,那么当它在同一个线程上运行时,就不会有使用另一个线程来执行工作的开销,因此如果它与其他
wait
s混合在一起,负面影响就会减少。因此,如果你真的需要返回
任务,但不能添加任何有用的内容,那么它是有用的你怎么称呼它呢

但是如果你真的不需要提供它,那就不要

(一个调用
Run()
的私有方法,因为每个调用站点都从中受益,这是不同的,在这里,您只是增加了方便性,而不是在多个地方调用
Run()
,但这应该有很好的文档记录)

最近我读了几篇SO帖子,其中建议使用Task.Run()是个坏主意,应该只在某些情况下使用,但这些情况似乎不是很清楚

我告诉那些不熟悉异步的人绝对简单的经验法则是:

首先,了解目的。异步是为了缓解高延迟操作的重要低效。

你正在做的事情是低延迟吗?那么,不要以任何方式使其异步。只要做这项工作。它很快。使用工具来缓解低延迟任务的延迟只会使你的程序变得不必要的复杂

你所做的事情是不是因为等待磁盘旋转或数据包显示而导致延迟很高?将其设置为异步,但不要将其放在另一个线程上。你不需要雇佣工作人员坐在邮箱旁等待信件到达;邮政系统已经对你异步运行。你不需要雇佣人员来实现这一点re异步。如果不清楚,请阅读“”

高延迟的工作是否需要等待CPU来完成一些巨大的计算?比如需要超过10毫秒的计算?然后将该任务转移到threa上
Task task = SomeLongRunningWayToGetName()
  .ContinueWith(t => GetMyObjectIdAsync(t.Result))
  .ContinueWith(t => _resource = GetResourceForIDAsync(t.Result));
public Task<Guid> GetMyObjectIdAsync(string objectName)
{
  return Task.FromResult(GetMyObjectId(objectName);
}