ChatGPT解决这个技术问题 Extra ChatGPT

Do you have to put Task.Run in a method to make it async?

I'm trying to understand async await in the simplest form. I want to create a very simple method that adds two numbers for the sake of this example, granted, it's no processing time at all, it's just a matter of formulating an example here.

Example 1

private async Task DoWork1Async()
{
    int result = 1 + 2;
}

Example 2

private async Task DoWork2Async()
{
    Task.Run( () =>
    {
        int result = 1 + 2;
    });
}

If I await DoWork1Async() will the code run synchronously or asynchronously?

Do I need to wrap the sync code with Task.Run to make the method awaitable AND asynchronous so as not to block the UI thread?

I'm trying to figure out if my method is a Task or returns Task<T> do I need to wrap the code with Task.Run to make it asynchronous.

I see examples on the net where people are awaiting code that has nothing async within and not wrapped in a Task.Run or StartNew.

Isn't your first snippet giving you any warnings?

S
Stephen Cleary

First, let's clear up some terminology: "asynchronous" (async) means that it may yield control back to the calling thread before it starts. In an async method, those "yield" points are await expressions.

This is very different than the term "asynchronous", as (mis)used by the MSDN documentation for years to mean "executes on a background thread".

To futher confuse the issue, async is very different than "awaitable"; there are some async methods whose return types are not awaitable, and many methods returning awaitable types that are not async.

Enough about what they aren't; here's what they are:

The async keyword allows an asynchronous method (that is, it allows await expressions). async methods may return Task, Task, or (if you must) void.

Any type that follows a certain pattern can be awaitable. The most common awaitable types are Task and Task.

So, if we reformulate your question to "how can I run an operation on a background thread in a way that it's awaitable", the answer is to use Task.Run:

private Task<int> DoWorkAsync() // No async because the method does not need await
{
  return Task.Run(() =>
  {
    return 1 + 2;
  });
}

(But this pattern is a poor approach; see below).

But if your question is "how do I create an async method that can yield back to its caller instead of blocking", the answer is to declare the method async and use await for its "yielding" points:

private async Task<int> GetWebPageHtmlSizeAsync()
{
  var client = new HttpClient();
  var html = await client.GetAsync("http://www.example.com/");
  return html.Length;
}

So, the basic pattern of things is to have async code depend on "awaitables" in its await expressions. These "awaitables" can be other async methods or just regular methods returning awaitables. Regular methods returning Task/Task<T> can use Task.Run to execute code on a background thread, or (more commonly) they can use TaskCompletionSource<T> or one of its shortcuts (TaskFactory.FromAsync, Task.FromResult, etc). I don't recommend wrapping an entire method in Task.Run; synchronous methods should have synchronous signatures, and it should be left up to the consumer whether it should be wrapped in a Task.Run:

private int DoWork()
{
  return 1 + 2;
}

private void MoreSynchronousProcessing()
{
  // Execute it directly (synchronously), since we are also a synchronous method.
  var result = DoWork();
  ...
}

private async Task DoVariousThingsFromTheUIThreadAsync()
{
  // I have a bunch of async work to do, and I am executed on the UI thread.
  var result = await Task.Run(() => DoWork());
  ...
}

I have an async/await intro on my blog; at the end are some good followup resources. The MSDN docs for async are unusually good, too.


@sgnsajgon: Yes. async methods must return Task, Task<T>, or void. Task and Task<T> are awaitable; void is not.
Actually, a async void method signature will compile, it's just a quite terrible idea as you loose your pointer to your async task
@TopinFrassi: Yes, they will compile, but void is not awaitable.
@ohadinho: No, what I'm talking about in the blog post is when the entire method is just a call to Task.Run (like DoWorkAsync in this answer). Using Task.Run to call a method from a UI context is appropriate (like DoVariousThingsFromTheUIThreadAsync).
Yes, exactly. It's valid to use Task.Run to invoke a method, but if there's a Task.Run around all (or almost all) of the method's code, then that's an anti-pattern - just keep that method synchronous and move the Task.Run up a level.
B
Beltway

One of the most important thing to remember when decorating a method with async is that at least there is one await operator inside the method. In your example, I would translate it as shown below using TaskCompletionSource.

private Task<int> DoWorkAsync()
{
    //create a task completion source
    //the type of the result value must be the same
    //as the type in the returning Task
    TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
    Task.Run(() =>
    {
        int result = 1 + 2;
        //set the result to TaskCompletionSource
        tcs.SetResult(result);
    });
    //return the Task
    return tcs.Task;
}

private async Task DoWork()
{
    int result = await DoWorkAsync();
}

Why do you use TaskCompletionSource, instead of just returning task returned by Task.Run() method (and changing its body to return result)?
Just a side note. A method that has a "async void" signature is generally bad practice and considered bad code as it can lead to UI deadlock pretty easily. The main exception is asynchronous event handlers.
No idea why async void is considered "bad practice", there are plenty of applications you would use it for, basically any time you need to do something that you don't care when it ends.
C
Craig.Feied

When you use Task.Run to run a method, Task gets a thread from threadpool to run that method. So from the UI thread's perspective, it is "asynchronous" as it doesn't block UI thread.This is fine for desktop application as you usually don't need many threads to take care of user interactions.

However, for web application each request is serviced by a thread-pool thread and thus the number of active requests can be increased by saving such threads. Frequently using threadpool threads to simulate async operation is not scalable for web applications.

True Async doesn't necessarily involving using a thread for I/O operations, such as file / DB access etc. You can read this to understand why I/O operation doesn't need threads. http://blog.stephencleary.com/2013/11/there-is-no-thread.html

In your simple example,it is a pure CPU-bound calculation, so using Task.Run is fine.


So if I have to consume a synchronous external api within a web api controller, I should NOT wrap the synchronous call in Task.Run()? As you said, doing so will keep the initial request thread unblocked but it's using another pool thread to call the external api. In fact I think it's still a good idea because doing this way it can in theory use two pool threads to process many requests e.g. one thread can process many incoming requests and another one can call the external api for all these requests?
I agree.I am not saying you shouldn't absolutely wrap all synchronous calls within Task.Run(). I am simply pointing out potential issue.
@stt106 I should NOT wrap the synchronous call in Task.Run() that's correct. If you do, you'd just be switching threads. i.e. you're unblocking the initial request thread but you're taking another thread from the threadpool which could have been used to process another request. The only outcome is a context switch overhead when the call is completed for absolutely zero gain