Reputation: 177
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
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
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