简体   繁体   中英

Async void or Task.Run?

Consider this method:

public Status SendMessage(InParam inParam)
{
    try
    {
        Task.Run(() => MethodAsync(inParam));
        return Status.Success;
    }
    catch (Exception ex)
    {
        // Log the exception
        return Status.Failed;
    }
}

The MethodAsync method:

public async Task<Status> MethodAsync(InParam inParam)
{
    try
    {
        return await processor.Process(inParam);
    }
    catch (Exception ex)
    {
        // Log exception
        return Status.Failed;
    }
}

and the Process method:

public async Task<Status> Process(InParam inParam)
{
    try
    {
         IMessage endpoint = (IMessage)Activator
            .CreateInstance(Type.GetType(_message.AgentDLLName), args);
         _messageDispatchers.Add(endpoint);

        foreach (IMessage dispatcher in _messageDispatchers)
        {
            await Task.Run(() => dispatcher.SendMessage(_message));
        }
        return await Task.Run(() => Status.Success);
    }
    catch (Exception ex)
    {
        // Log exception
        return Status.Failed;
    }

}

So the use case is that at the end of some processing workflow an email has to be sent. This email sending part was taking some time and the user had to wait, so the original developer put this code.

I am trying to make SendMessage call MethodAsync and should not wait for it to return. I have read that in an async workflow the complete stack needs to be async for it to work properly. The SendMessage is not marked async.

Is this the correct way to call MethodAsync since Task.Run returns an awaitable?

As MethodAsync is marked as async the call Task.Run(() => MethodAsync(inParam)); does not make much sense.
If you want to implement it as a " fire-and-forget "-call ( BAD by the way ) you can simply call MethodAsync(inParam); , because this also starts the await ed method inside MethodAsync "in its own task" (simplified) and returns that. If you then do not "wait that awaitable" your code inside SendMessage will continue to execute while it is still running.

BUT as already said: " fire-and-forget " is bad design in almost all cases. Can you explain your use-case a little more, so we may can provide a better approach?

UPDATE:
If there is REALLY no way to either make SendMessage async or have a synchronous counterpart of MethodAsync I recommend the following:

public Status SendMessage(InParam inParam)
{
    try
    {
        return AsyncPump.Run(() => MethodAsync(inParam));
    }
    catch (Exception ex)
    {
        // Log the exception
        return Status.Failed;
    }
}

Using the AsyncPump you can return "the real result" and have no deadlocking problems.
In your example implementation of SendMessage the try / catch also makes less sense, as the method will very likely return way before any exeption will happen inside MethodAsync .

UPDATE 2 (after updated question):
I would recommend "going async all the way". Meaning also make SendMessage async (and all methods up to the UI) so you can await the "real result", but do not lock the UI while you are waiting...

UPDATE 3:
I would also change

foreach (IMessage dispatcher in _messageDispatchers)
{
    await Task.Run(() => dispatcher.SendMessage(_message));
}

to

await Task.Run(() =>
    {
        foreach (IMessage dispatcher in _messageDispatchers)
            dispatcher.SendMessage(_message);
    });

This casues less context-switches. Or even:

await Task.Run(() => Parallel.ForEach(_messageDispatchers, d => d.SendMessage(_message)));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM