2015-08-27 46 views
3

我正在使用訪問令牌/刷新令牌讓用戶保持登錄狀態的項目。我將這些值存儲在cookie中,無論用戶何時訪問該網站,我都希望自動登錄他,而不管他用來訪問該網站的頁面。爲此,我創建了一個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爲異步方法的工作,但結果。陷入僵局。除此之外,我閱讀了關於這個問題的許多線索,並最終找到了一個能夠使登錄工作的人:How would I run an async Task<T> method synchronously?。這是有點哈克雖然與這個輔助工作:

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

如果我再更改BaseController構造函數的內容:

AsyncHelpers.RunSync(() => LoginModel.SetUserFromAuthenticationCookie()); 

該功能可以運行如預期。

我想知道如果您對如何以更好的方式做到這一點有任何建議。也許我應該把電話轉到SetUserFromAuthenticationCookie()到另一個地方,但是現在我不知道那會是什麼。

+0

你不會使用'await LoginModel.SetUserFromAuthenticationCookie();'? – JamieD77

+0

或'LoginModel.SetUserFromAuthenticationCookie()。RunSynchronously();' – JamieD77

+0

我無法回答關於異步的東西......但是如果您希望在每個動作之前執行此代碼,您可以考慮創建一個全局動作過濾器。 – Peter

回答

6

我在另一個堆棧上找到了這個解決方案。 Synchronously waiting for an async operation, and why does Wait() freeze the program here

你的構造函數需要看起來像這樣。

public BaseController() 
{ 
    var task = Task.Run(async() => { await LoginModel.SetUserFromAuthenticationCookie(); }); 
    task.Wait(); 
} 
+0

我不知道我可以在任務的運行功能中使用async/await。感謝您的領導!我現在遇到的問題是,我對某些部分依賴於HttpContext.Current.xxx,並在新創建的線程HttpContext.Current(顯然)爲空。我已經計劃重構我的代碼的這些部分,現在就開始着手這方面的工作。在完成這些之後,知道你的建議是否能解決我的問題,我會回到這個問題。感謝您迄今爲止的幫助! – user1796440