2

I am very new with this async/await concept, so I apologise for asking anything that is obvious.

I need to send email and the new API require me to use async and await. Problem is that a lot of my methods need to call this "send email" synchronously.

So, I create a synchronous wrapper method:

private Task SendEmailAsync(string email, string content)
{
   ...
   ...
   ...
}

private void SendEmail(string email, string content)
{
    Task tsk = Task.Factory.StartNew(async () => await SendEmailAsync(email, content));
    try { tsk.Wait(); }
    catch (AggregateException aex)
    {
        throw aex.Flatten();
    }
}

But for some reason, tsk.Wait() does not waiting for await SendEmailAsync(...) to finish. So, I need to add ManualResetEvent. Something like this

private void SendEmail(string email, string content)
{
    ManualResetEvent mre = new ManualResetEvent(false);
    Task tsk = Task.Factory.StartNew(async () =>
    {
        mre.Reset();
        try { await SendEmailAsync(email, content); }
        finally { mre.Set(); }
    });

    mre.WaitOne();
    try { tsk.Wait(); }
    catch (AggregateException aex)
    {
        throw aex.Flatten();
    }
}

But any exception thrown by SendEmailAsync(...) will NOT be captured by tsk.Wait(). My question is:

  1. Why tsk.Wait() does NOT wait for await SendEmailAsync(...)
  2. How to catch exception thrown by await SendEmailAsync(...)

Thanks!

RPichioli
  • 3,245
  • 2
  • 25
  • 29
Sam
  • 1,826
  • 26
  • 58
  • 1
    Just call `SendEmailAsync(email, content).GetAwaiter().GetResult();` – Matias Cicero Dec 06 '16 at 12:53
  • 2
    For running async code in a sync manner, see here http://stackoverflow.com/a/5097066/5062791 – ColinM Dec 06 '16 at 12:54
  • 5
    You shouldn't do "sync over async" as it tends to cause deadlocks! Stephen Cleary will be here soon to point that out ;-), but if you really have to I can recommend the AsyncPump from Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/). But that should not be used "inside" your libary. – Christoph Fink Dec 06 '16 at 12:55
  • 1
    For reference to Stephen Cleary's blog regarding this, http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – ColinM Dec 06 '16 at 12:57
  • 1
    [StartNew is Dangerous](http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html): "... since StartNew does not understand async delegates, what that task actually represents is just the beginning of that delegate ..." – Damien_The_Unbeliever Dec 06 '16 at 12:57
  • Yes, I have tried to use `Task.Result` and `Task.GetAwaiter().GetResult()` but it resulted in deadlock. I have tried solution posted by ColinM in stackoverflow link and it worked. @ColinM please posted your answer and I will accept it. Thanks! – Sam Dec 06 '16 at 22:16

3 Answers3

2

You can run asynchronous code in a synchronous manner by using the following extensions.

https://stackoverflow.com/a/5097066/5062791

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}
Community
  • 1
  • 1
ColinM
  • 2,622
  • 17
  • 29
1

I should start by stating that you should follow @ChrFin's comment and try to refactor your code to make it asynchronous, instead of trying to run an existing async library synchronously (You'll even note an improvement in the application's performance).


To accomplish what you seek, you should use SendEmailAsync(email, content).GetAwaiter().GetResult().

Why not SendEmailAsync(email, content).Wait()? you may ask.

Well, there's a subtle difference. Wait() will wrap any runtime exception inside an AggregateException which will make your life harder if you attempt to catch the original one.

GetAwaiter().GetResult() will simply throw the original exception.

You code would look like this:

private void SendEmail(string email, string content)
{
    try
    {
        SendEmailAsync(email, content).GetAwaiter().GetResult();
    }
    catch(Exception ex)
    {
        // ex is the original exception
        // Handle ex or rethrow or don't even catch
    }
}
Matias Cicero
  • 25,439
  • 13
  • 82
  • 154
  • Hi Matias, I should have mentioned that I've tried to use both `Task.Result` and `Task.GetAwaiter().GetResult()`, but both methods caused deadlock in my case (MVC .NET) which forced me to try to use `Task.Factory.StartNew()` which is also wrong. But I have used the solution posted by ColinM and it worked fine. Thanks again for your help. – Sam Dec 06 '16 at 22:20
1

I need to send email and the new API require me to use async and await. Problem is that a lot of my methods need to call this "send email" synchronously.

The best solution is to remove the "synchronous caller" requirement. Instead, you should allow async and await to grow naturally through your codebase.

for some reason, tsk.Wait() does not waiting for await SendEmailAsync(...) to finish.

That's because you're using Task.Factory.StartNew, which is a dangerous API.

I have tried to use Task.Result and Task.GetAwaiter().GetResult() but it resulted in deadlock.

I explain this deadlock in detail on my blog.

I have used the solution posted by ColinM and it worked fine.

That's a dangerous solution. I don't recommend its use unless you understand exactly how it works.

If you absolutely must implement the antipattern of invoking asynchronous code synchronously, then you'll need to use a hack to do so. I cover the various hacks in an article on brownfield async. In your case, you could probably just use the thread pool hack, which is just one line of code:

private void SendEmail(string email, string content) =>
    Task.Run(() => SendEmailAsync(email, content)).GetAwaiter().GetResult();

However, as I stated at the beginning of this answer, the ideal solution is to just allow async to grow. Hacks like this limit the scalability of your web server.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810