0

I want to convert a (relatively) slow event handler to async to avoid blocking the other event subscribers. In the example below I do not want the other subscribers to the Message event to have to wait.

The only thing I can think of looks like "cheating":

client.Message += async (object sender, MessageEventArgs e) => {
    await Task.Run(() => { });
    logger.Info("Some info {0}", e.Message);
}

Of course I could wrap the whole logger.Info call in the Task, but I don't see why that is better. I just looks less like cheating.

My only other thought is that the handler should be left synchronous, and the Message event should only be subscribed to when latency is not critical. So I could have a second event that is only used by low latency async subscribers.

I am looking for feedback on the proper coding design here.

1
  • Comments are not for extended discussion; this conversation has been moved to chat. Commented Aug 6, 2020 at 6:17

1 Answer 1

1

You can use Task.Yield if you want to attach an asynchronous handler that will be invoked after all synchronous handlers of the same event have been invoked, and you also want the asynchronous invocation to occur on the current synchronization context. For a GUI application this means that you want the handler to run on the UI thread.

client.Message += async (sender, e) =>
{
    await Task.Yield();
    logger.Info("Some info {0}", e.Message);
}

Don't use await Task.Run(() => { }), because it's just a verbose, idiomatic, less efficient and (possibly) less robust alternative of await Task.Yield().

If you want to attach an asynchronous handler that will run on a ThreadPool thread (instead of the current synchronization context), use Task.Run:

client.Message += async (sender, e) =>
{
    await Task.Run(() => logger.Info("Some info {0}", e.Message));
}

You should only do that if you are sure that the logger object is thread-safe, supports concurrent operations, and doesn't require thread-affinity (like many COM components do).

Sign up to request clarification or add additional context in comments.

3 Comments

Perhaps I should update my question, but there is still one thing I am not clear on: My understanding is that the logger.Info call takes a negligible amount of time. Yet I noticed a measurable delay, in other tasks in the current synchronization context, when the above handler was synchronous instead of async. So the question is: does it still make sense performance-wise to use an async handler for an operation that takes a negligible amount of time?
@James Hirschorn the behavior you are observing is quite inexplicable, and the workaround you discovered is equally surprising that it works. Having an inexplicable problem that is fixed by an inexplicable solution is not good news for the long term maintainability of your application. I suggest that you update your question with more info about the problem, so that we have a better idea about what's going on. How much is the delay when attaching the sync handler, how much is when attaching the async handler, and how did you measure it?
I am also weary of inexplicable solutions. I will need to investigate further, and perhaps ask a new question once the performance issues become more clear.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.