Wednesday, June 20, 2018

c# - What is the correct way to call an async method from synchronous code, and block until the task is complete?

Consider this code:



public async Task DoStuffAsync() {
await WhateverAsync(); // Stuff that might take a while
}

// Elsewhere in my project...
public async Task MyMethodAsync() {

await DoStuffAsync();
}

public void MyMethod() {
DoStuffAsync(); // <-- I want this to block until Whatever() is complete.
}


I want to provide two methods with the same functionality: an async method which runs asynchronously, and a non-async method that blocks, and either of these two methods can be called depending on whether the code I'm calling from is also async.




I don't want to repeat loads of code everywhere and create two versions of each method, wasting time and effort and reducing maintanability.



I'm guessing I can do something like one of the following:



// Call it just like any other method... 
// the return type would be Task though which isn't right.
DoStuffAsync();

// Wrap it in a Task.Run(...) call, this seems ok, but
// but is doesn't block, and Task.Run(...) itself returns an

// awaitable Task... back to square one.
Task.Run(() => DoStuffAsync());

// Looks like it might work, but what if I want to return a value?
DoStuffAsync().Wait();


What is the correct way to do this?



UPDATE




Thanks for your answers. The general advice seems to be just provide an Async method and let the consumer decide, rather than creating a wrapper method.



However, let me try to explain my real-world problem...



I have a UnitOfWork class that wraps the the SaveChanges() and SaveChangesAsync() methods of an EF6 DbContext. I also have a List of emails that need to be sent only when the SaveChanges() succeeds. So my code looks like this:



private readonly IDbContext _ctx;
private readonly IEmailService _email;
private readonly List _emailQueue;


// ....other stuff

public void Save() {
try {
_ctx.SaveChanges();
foreach (var email in _emailQueue) _email.SendAsync(email).Wait();

} catch {
throw;

}
}

public async Task SaveAsync() {
try {
await _ctx.SaveChangesAsync();
foreach (var email in _emailQueue) await _email.SendAsync(email);

} catch {
throw;

}
}


So you can see that because an EF6 DbContext provides an async and non-async version, I kinda have to as well... Based on the answers so far, I've added .Wait() to my SendAsync(email) method, which asynchronously sends out an email via SMTP.



So... all good, apart from the deadlock problem... how do I avoid a deadlock?

No comments:

Post a Comment

plot explanation - Why did Peaches&#39; mom hang on the tree? - Movies &amp; TV

In the middle of the movie Ice Age: Continental Drift Peaches' mom asked Peaches to go to sleep. Then, she hung on the tree. This parti...