2016-12-06 2 views
1

Я очень новичок в этой концепции async/await, поэтому я прошу прощения за то, что я прояснил.Async и ждут синхронного метода, используя Task

Мне нужно отправить электронное письмо, и новый API потребует от меня использовать async и ждать. Проблема в том, что многие мои методы должны называть это «отправить электронную почту» синхронно.

Итак, я создаю синхронный метод обертку:

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(); 
    } 
} 

Но по какой-то причине, tsk.Wait() не ждет AWAIT SendEmailAsync (...) закончить. Итак, мне нужно добавить ManualResetEvent. Что-то вроде этого

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(); 
    } 
} 

Но каких-либо исключений, брошенной SendEmailAsync (...) НЕ будет захвачен tsk.Wait(). Мой вопрос:

  1. Почему tsk.Wait() не ждет ждать SendEmailAsync (...)
  2. Как поймать исключение брошенный ждут SendEmailAsync (...)

Спасибо!

+1

Просто позвоните 'SendEmailAsync (электронная почта, содержание) .GetAwaiter() GetResult();.' –

+2

Для запуска кода асинхронной в синхронизации таким образом, смотрите здесь http://stackoverflow.com/a/5097066/5062791 – ColinM

+5

вы не должны «делать» синхронизировать через async », поскольку он имеет тенденцию вызывать взаимоблокировки! Стивен Клири скоро будет здесь, чтобы указать это ;-), но если вам действительно нужно, я могу порекомендовать AsyncPump от Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await -synchronizationcontext-и-консольных приложений /). Но это не должно использоваться «внутри» вашей библиотеки. – ChrFin

ответ

0

Вы можете запустить асинхронный код в синхронном режиме с помощью следующих расширений.

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; 
     } 
    } 
} 
+0

Это сработало! Спасибо, Колин! – Sam

1

Я должен начать с заявления о том, что вы должны следовать комментарию @ ChrFin и попытаться реорганизовать свой код, чтобы сделать его асинхронным, вместо того, чтобы синхронно запускать существующую асинхронную библиотеку (вы даже заметите улучшение производительности приложения) ,


Чтобы выполнить то, что вы ищете, вы должны использовать SendEmailAsync(email, content).GetAwaiter().GetResult().

Почему не SendEmailAsync(email, content).Wait()? вы можете спросить.

Ну, есть тонкая разница. Wait() перенесет любое исключение во время выполнения в AggregateException, что сделает вашу жизнь более сложной, если вы попытаетесь поймать оригинал.

GetAwaiter().GetResult() просто выбросит исходное исключение.

Вы код будет выглядеть следующим образом:

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 
    } 
} 
+0

Привет, Матиас, я должен был упомянуть, что я пытался использовать «Task.Result» и «Task.GetAwaiter(). GetResult()», но оба метода вызвали тупик в моем случае (MVC .NET), которые заставили меня попытаться использовать 'Task.Factory.StartNew()', что также неверно. Но я использовал решение, отправленное ColinM, и оно отлично работало. В очередной раз благодарим за помощь. – Sam

0

Мне нужно отправить электронную почту, и новый API требует, чтобы я использовал асинхра и ждать. Проблема в том, что многие мои методы требуют синхронного вызова «отправить электронную почту».

лучший решение удалить «синхронное абоненте» требование. Вместо этого вы должны позволить async и await расти естественно через вашу кодовую базу.

по какой-либо причине, tsk.Wait() не ждет ожидания SendEmailAsync (...) для завершения.

Это потому, что вы используете Task.Factory.StartNew, что является dangerous API.

Я попытался использовать Task.Result и Task.GetAwaiter(). GetResult(), но это привело к тупиковой ситуации.

Я объясняю этот тупик подробно on my blog.

Я использовал решение, отправленное ColinM, и оно отлично работало.

Это опасное решение. Я не рекомендую его использовать, если вы не понимаете точно как это работает.

Если вы обязательно должны использовать синхронный вызов асинхронного кода вызова, тогда вам нужно использовать взломать его. Я охватываю различные хаки в статье на brownfield async. В вашем случае, вы могли бы, вероятно, просто использовать хак пул потоков, который является только одна строка кода:

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

Однако, как я уже говорил в начале этого ответа, идеальным решением является просто позволить async расти. Такие хаки, как это, ограничивают масштабируемость вашего веб-сервера.

Смежные вопросы