Asp.net-Mvc
從控制器的建構子執行非同步方法的問題
我正在開發一個項目,我希望讓使用者使用訪問令牌/刷新令牌保持登錄狀態。我將這些值儲存在 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 { /// <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()到另一個位置,但此時我不知道那會在哪裡。
我在另一個堆棧上找到了這個解決方案。 同步等待一個非同步操作,為什麼Wait()會在這裡凍結程序
您的建構子需要看起來像這樣。
public BaseController() { var task = Task.Run(async () => { await LoginModel.SetUserFromAuthenticationCookie(); }); task.Wait(); }