How can I await an async method without an async m

2019-03-18 17:48发布

问题:

I have a method that I want to await but I don't want to cause a domino effect thinking anything can call this calling method and await it. For example, I have this method:

public bool Save(string data)
{
   int rowsAffected = await UpdateDataAsync(data);
   return rowsAffected > 0;
}

I'm calling:

public Task<int> UpdateDataAsync()
{
  return Task.Run(() =>
  {
    return Data.Update(); //return an integer of rowsAffected
  }
}

This won't work because I have to put "async" in the method signature for Save() and then I can't return bool I have to make it Task<bool> but I don't want anyone awaiting the Save() method.

Is there a way I can suspend the code execution like await or somehow await this code without the async modifier?

回答1:

How can I await an async method without an async modifier in this parent method?

That's kind of like asking "how can I write an application using C# but without taking a dependency on any kind of .NET runtime?"

Short answer: don't do that.

Really, what you're doing here is taking a naturally-synchronous method (Update), making it appear asynchronous by running it on a thread pool thread (UpdateDataAsync), and then you're wanting to block on it to make the asynchronous method appear synchronous (Save). Serious red flags.

I recommend you carefully study Stephen Toub's famous pair of blog posts should I expose asynchronous wrappers for my synchronous methods and should I expose synchronous wrappers for my asynchronous methods. The answer to both questions is "no", though Stephen Toub explains several options to do it if you really have to.

That "really have to" should be reserved for the application level. I assume these methods (Update, UpdateDataAsync, and Save) are in different layers of the application (e.g., data / data service / view model). The data / data service layers should not be doing synchronous/asynchronous conversions. The view model (application-specific) level is the only one that has an excuse to do that kind of conversion -- and it should only do so as a last resort.



回答2:

Edit: this answer was before the Task.Run was added. With that extra context, the scenario is best described as "don't do that".


You can access .Result or use .Wait(), but you need to know how the task is implemented first. In particular, you need to know whether it uses sync-context. The reason this is important is that if it does this could deadlock immediately, because some sync-contexts need the calling context to exit completely (for example, MVC's sync-context needs to leave the controller's action method).

To guard against this is hard, but you should probably always explicitly specify a timeout with a call to .Wait() - just in case.