Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling synchronous method from asynchronous method

I'm implementing EmailServer for ASP.NET Identity.

I don't like how async...await is not compatible with using, and so my email method is synchronous.

So how can I call it from the framework's SendAsync method?

public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        Email email = new Email();
        return Task.FromResult<void>(email.Send(message));
    }
}

In the code above, Task.FromResult() gives me an error saying void can't be used as an argument type. But email.Send() returns void!

How to get out of this quagmire?

like image 971
Jonathan Wood Avatar asked Jan 04 '23 02:01

Jonathan Wood


1 Answers

If you don't have a result, then don't try to return a result. Just return a plain, completed Task:

public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        new Email().Send(message);
        return Task.CompletedTask;
    }
}

If you are stuck in pre-4.6 land, then you can use Task.FromResult<bool>(true) instead.

All that said, I'm confused by your comment about "async...await is not compatible with using". In my experience, that works fine. And it would be much better if your method were in fact asynchronous. I think you would be better served by focusing on how to do that, rather than what the best/correct syntax is for faking an async method.

Addendum:

I am still not clear on your concern about the use of using. However, based on your comment, it seems you would like to use SmtpClient.SendAsync() but are uncertain as to how to apply that in the context of async/await.

Unfortunately, even before async/await, we had lots of asynchronous methods in .NET, and those methods used the same naming as the convention for new awaitable methods. (To be clear: it's the naming that's unfortunate, not that the async methods existed :) ). However, in all cases, it is possible to adapt the old API to the new.

In some cases, it's as simple as using the Task.FromAsync() method. That works with anything that supports the old Begin.../End... model. But the SmtpClient.SendAsync() model is an event-based callback approach, which requires a slightly different approach.

NOTE: I noticed after writing the example below that the SmtpClient class has a Task-based method for asynchronous operation, SendMailAsync(). So in reality, there's no need to adapt the older SendAsync() method. But it's a useful example to use, to show how one might do such adaptation when a Task-based alternative hasn't been provided.

Briefly, you can use TaskCompletionSource with the SendCompleted event on the SmtpClient object. Here's an outline of what that would look like:

public class EmailService : IIdentityMessageService
{
    public async Task SendAsync(IdentityMessage message)
    {
        // I'm not familiar with "IdentityMessage". Let's assume for the sake
        // of this example that you can somehow adapt it to the "MailMessage"
        // type required by the "SmtpClient" object. That's a whole other question.
        // Here, "IdentityToMailMessage" is some hypothetical method you write
        // to handle that. I have no idea what goes inside that. :)
        using (MailMessage mailMessage = IdentityToMailMessage(message))
        using (SmtpClient smtpClient = new SmtpClient())
        {
            TaskCompletionSource<bool> taskSource = new TaskCompletionSource<bool>();

            // Configure "smtpClient" as needed, such as provided host information.
            // Not shown here!

            smtpClient.SendCompleted += (sender, e) => taskSource.SetResult(true);
            smtpClient.SendAsync(mailMessage, null);

            await taskSource.Task;
        }
    }
}

The above will start the asynchronous operation, and use the SendCompleted event's handler (i.e. the "callback" to which the documentation refers) to set the result for the TaskCompletionSource<bool> object (the result value is never really used, but there's no plain-vanilla Task version of TaskCompletionSource…you have to have some value).

It uses await, rather than returning the taskSource.Task object directly, because that way it can correctly handle disposing the SmtpClient object when the email operation has in fact completed.

like image 72
Peter Duniho Avatar answered Jan 13 '23 12:01

Peter Duniho