如何在不使用 Task.Delay 或 await 的情况下编写异步方法

WPF*_*guy 3 c# asynchronous task async-await

我正在尝试学习编写自己的异步方法,但遇到了困难,因为我在网上看到的数百万个示例都await Task.Delay在自定义异步方法中使用,而且我不想在我的代码中添加延迟,也没有任何其他异步方法可以代替它调用。

让我们使用一个简单的例子,我想从一个庞大的现有对象集合中创建一个只有两个属性的新对象集合,每个对象都有很多属性。假设这是我的同步代码:

public List<SomeLightType> ToLightCollection(List<SomeType> collection)
{
    List<SomeLightType> lightCollection = new()
    foreach (SomeType item in collection)
    {
        lightCollection.Add(new SomeLightType(item.Id, item.Name));
    }
    return lightCollection;
}
Run Code Online (Sandbox Code Playgroud)

要使该方法异步,是否只需要将其包装在 a 中Task.Runasync在方法名称上添加关键字和后缀,并更改返回类型,如下所示?:

public Task<List<SomeLightType>> ToLightCollectionAsync(List<SomeType> collection)
{
    List<SomeLightType> lightCollection = new()
    Task.Run(() =>
    {
        foreach (SomeType item in collection)
        {
            lightCollection.Add(new SomeLightType(item.Id, item.Name));
        }
    });
    return lightCollection;
}
Run Code Online (Sandbox Code Playgroud)

或者我还需要await返回Task方法内部吗?(编译器给了我一个警告,直到我添加await.):

public async Task<List<SomeLightType>> ToLightCollectionAsync(List<SomeType> collection)
{
    List<SomeLightType> lightCollection = new()
    await Task.Run(() =>
    {
        foreach (SomeType item in collection)
        {
            lightCollection.Add(new SomeLightType(item.Id, item.Name));
        }
    });
    return lightCollection;
}
Run Code Online (Sandbox Code Playgroud)

编辑:

哦,是的,我刚刚意识到我需要进行await此操作,否则将在填充之前返回空集合。但是,这是使这段代码异步运行的正确方法吗?

Ste*_*ary 13

我在网上看到的数百万个示例都在自定义异步方法中使用 await Task.Delay,我既不想在我的代码中添加延迟,也不想在其位置调用任何其他异步方法。

Task.Delay 通常用作“占位符”,意思是“用您的实际异步工作替换它”。

我正在尝试学习编写自己的异步方法

异步代码从“另一端”开始。最常见的示例是 I/O 操作:您可以使其异步而不是阻塞调用线程。在最低级别,这通常是使用 a 完成的TaskCompletionSource<T>,它创建了一个Task<T>您可以立即返回的,然后在操作完成后,您可以使用TaskCompletionSource<T>来完成Task<T>.

但是,正如您在评论中所述:

我绝对希望该方法异步运行,因为它目前需要几分钟......这是一个 WPF 应用程序

你真正想要的不是异步代码;你想在后台线程上运行一些代码,这样它就不会阻塞 UI 线程。正在运行的代码受 CPU 限制并且没有 I/O 要做,所以它只是在线程池线程上运行,而不是实际上是异步的。

让我们使用一个简单的例子......为了使这个方法异步......

要在后台线程上运行此代码,您可以使用Task.Run. 不过,我建议你不要执行使用这种方法Task.Run。如果你这样做了,那么你的方法看起来是异步的,但实际上并不是异步的;它只是在线程池线程上同步运行——我称之为“假异步”(它有一个异步签名,但实际上不是异步的)。

IMO,保持业务逻辑同步更清晰,在这种情况下,由于您想释放 UI 线程,请让UI代码使用Task.Run以下命令调用它:

// no change
public List<SomeLightType> ToLightCollection(List<SomeType> collection)
{
    List<SomeLightType> lightCollection = new()
    foreach (SomeType item in collection)
    {
        lightCollection.Add(new SomeLightType(item.Id, item.Name));
    }
    return lightCollection;
}

async void Button_Click(...)
{
  var source = ...
  var lights = await Task.Run(() => ToLightCollection(source));
  ... // Do something with lights
}
Run Code Online (Sandbox Code Playgroud)

  • 感谢您花时间如此雄辩地解释所有这些,斯蒂芬。 (3认同)

Mic*_*ael 3

Task.Run 用于 CPU 密集型工作(请参阅learn.microsoft.com - 深入异步

await Task.Run()如果直接返回创建的任务,就可以避免这种情况:

public Task<List<SomeLightType>> ToLightCollectionAsync(List<SomeType> collection) => Task.Run(() =>
{
    List<SomeLightType> lightCollection = new();
    // Do CPU bound work
    foreach (SomeType item in collection)
    {
        lightCollection.Add(new SomeLightType(item.Id, item.Name));
    }
    return lightCollection;
});
Run Code Online (Sandbox Code Playgroud)

现在,调用者可以在异步方法中等待结果,以保持 UI 响应:

public async Task CallerMethod()
{
    // ...
    var result = await ToLightCollectionAsync(collection);
}
Run Code Online (Sandbox Code Playgroud)

您还有机会在此计算期间执行一些工作。

public async Task CallerMethod()
{
    var task = ToLightCollectionAsync(collection);
    // Do some other work
    var result = await task;
}
Run Code Online (Sandbox Code Playgroud)

  • 并发和并行是不同的东西。如果你想并行计算,你可以将源列表分成 n 部分。如果将 ToLightCollectionAsync 的输入参数更改为 IEnumerable&lt;T&gt;,则可以使用 LINQ 来实现此目的。然后,您可以在不等待的情况下为每个部分调用此方法(`var task1 = ...; var task2 = ...`。最后,您可以等待每个启动的任务来收集结果:`var result1 = wait task1; var result2 = wait task2;`。最后,如果需要,您可以将结果与 Concat 合并。(使用共享输出列表需要锁定并降低性能)。 (2认同)