WPF guy
WPF guy

Reputation: 177

How to write an asynchronous method WITHOUT using Task.Delay OR await

I'm trying to learn to write my own asynchronous methods, but I'm having difficulty, because 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.

Let's use a simple example, where I want to create a new collection of objects, with only two properties, from a huge existing collection of objects, that each have a great many properties. Let's say this is my synchronous code:

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

To make this method asynchronous, do I just need to wrap it in a Task.Run, add the async keyword and suffix on the method name, and change the return type, as follows?:

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

Or do I also need to await the return of the Task inside the method? (The compiler gave me a warning until I added 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;
}

EDIT:

Oh yes, I have just realised that I need to await this operation, otherwise the empty collection will be returned before it is populated. But still, is this the correct way to make this code run asynchronously?

Upvotes: 5

Views: 2453

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 456497

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 is commonly used as a "placeholder" meaning "replace this with your actual asynchronous work".

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

Asynchronous code begins at the "other end". The most common example is with an I/O operation: you can make this asynchronous instead of blocking the calling thread. At the lowest level, this is commonly done using a TaskCompletionSource<T>, which creates a Task<T> you can return immediately, and then later when the operation completes, you can use the TaskCompletionSource<T> to complete the Task<T>.

However, as you state in the comments:

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

What you really want is not asynchronous code; you want to run some code on a background thread so it doesn't block the UI thread. The code being run is CPU-bound and has no I/O to do, so it's just going to run on a thread pool thread instead of actually being asynchronous.

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

To run this code on a background thread, you would use Task.Run. However, I recommend that you do not implement this method using Task.Run. If you do, then you have a method that looks asynchronous but is not actually asynchronous; it's just running synchronously on a thread pool thread - what I call "fake asynchronous" (it has an asynchronous signature but is not actually asynchronous).

IMO, it's cleaner to keep your business logic synchronous, and in this case since you want to free up the UI thread, have the UI code call it using 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
}

Upvotes: 18

Michael
Michael

Reputation: 1276

Task.Run is for CPU-Bound work (see learn.microsoft.com - Async in depth.

You can avoid await Task.Run() scenarios if you return the created task directly:

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

Now the caller can await for the result in an async Method to keep your UI responsive:

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

You also have the opportunity to perform some work during this computation.

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

Upvotes: 3

Related Questions