Asp.net-Mvc

從控制器的建構子執行非同步方法的問題

  • June 16, 2017

我正在開發一個項目,我希望讓使用者使用訪問令牌/刷新令牌保持登錄狀態。我將這些值儲存在 cookie 中,每當使用者訪問該站點時,我都希望他自動登錄,而不管他用於訪問該站點的頁面是什麼。為此,我創建了一個 BaseController,所有其他控制器都繼承自該控制器。BaseController 看起來像這樣:

public abstract class BaseController : Controller
{
   public BaseController()
   {
       LoginModel.SetUserFromAuthenticationCookie();
   }
}

每次執行操作之前都會執行此建構子,因此正是我想要的。問題是這SetUserFromAuthenticationCookie()是一個非同步方法,因為它必須呼叫其他非同步方法。它看起來像這樣:

public async static Task SetUserFromAuthenticationCookie()
   {
       // Check if the authentication cookie is set and the User is null
       if (AuthenticationRepository != null && User == null)
       {
           Api api = new Api();

           // If a new authentication cookie was successfully created
           if (await AuthenticationRepository.CreateNewAuthenticationCookieAsync())
           {
               var response = await api.Request(HttpMethod.Get, "api/user/mycredentials");

               if(response.IsSuccessStatusCode)
               {
                   User = api.serializer.Deserialize<UserViewModel>(await response.Content.ReadAsStringAsync());
               }
           }
       }
   }

問題是執行順序與我預期的不一樣,因此使用者沒有登錄。我嘗試使用.Result非同步方法,但這導致了死鎖。除此之外,我在 SO 上閱讀了許多關於該問題的執行緒,最終還找到了一個設法讓登錄工作的執行緒:如何同步執行非同步 Task<T> 方法?. 雖然它有點 hacky,但可以與這個助手一起使用:

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

       SynchronizationContext.SetSynchronizationContext(oldContext);
   }

   /// &lt;summary&gt;
   /// Execute's an async Task&lt;T&gt; method which has a T return type synchronously
   /// &lt;/summary&gt;
   /// &lt;typeparam name="T"&gt;Return Type&lt;/typeparam&gt;
   /// &lt;param name="task"&gt;Task&lt;T&gt; method to execute&lt;/param&gt;
   /// &lt;returns&gt;&lt;/returns&gt;
   public static T RunSync&lt;T&gt;(Func&lt;Task&lt;T&gt;&gt; task)
   {
       var oldContext = SynchronizationContext.Current;
       var synch = new ExclusiveSynchronizationContext();
       SynchronizationContext.SetSynchronizationContext(synch);
       T ret = default(T);
       synch.Post(async _ =&gt;
       {
           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&lt;Tuple&lt;SendOrPostCallback, object&gt;&gt; items =
           new Queue&lt;Tuple&lt;SendOrPostCallback, object&gt;&gt;();

       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(_ =&gt; done = true, null);
       }

       public void BeginMessageLoop()
       {
           while (!done)
           {
               Tuple&lt;SendOrPostCallback, object&gt; task = null;
               lock (items)
               {
                   if (items.Count &gt; 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;
       }
   }

如果我將 BaseController 建構子的內容更改為:

AsyncHelpers.RunSync(() =&gt; LoginModel.SetUserFromAuthenticationCookie());

該功能按預期工作。

我想知道您是否對如何以更好的方式執行此操作有任何建議。也許我應該將呼叫轉移SetUserFromAuthenticationCookie()到另一個位置,但此時我不知道那會在哪裡。

我在另一個堆棧上找到了這個解決方案。 同步等待一個非同步操作,為什麼Wait()會在這裡凍結程序

您的建構子需要看起來像這樣。

public BaseController()
{
   var task = Task.Run(async () =&gt; { await LoginModel.SetUserFromAuthenticationCookie(); });
   task.Wait();
}

引用自:https://stackoverflow.com/questions/32251596