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

How to write an asynchronous method WITHOUT using Task.Delay OR 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;
}

要使此方法异步,是否只需要将其包装在Task.Run 中,在方法名称上添加async 关键字和后缀,并更改return 类型, 如下?:

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;
}

或者我还需要await方法里面的Task的return吗? (编译器给了我一个警告,直到我添加 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;
}

编辑:

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

Task.Run 用于 CPU-绑定作品(参见 docs.microsoft.com - Async in depth

如果你直接return创建任务,你可以避免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;
});

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

public async Task CallerMethod()
{
    // ...
    var result = await ToLightCollectionAsync(collection);
}

您还有机会在此计算过程中执行一些工作。

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

ALL of the millions of examples that I have seen online ALL use await Task.Delay inside the custom async method and I neither want to add a delay into my code, nor have any other async method to call in its place.

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

I'm trying to learn to write my own asynchronous methods

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

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

I definitely want that method to run asynchronously, as it currently takes several minutes... this is a WPF application

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

Let's use a simple example... To make this method asynchronous...

要在后台线程上 运行 此代码,您将使用 Task.Run。然而,I recommend that you do not implement this method using 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
}