C#~非同步程式設計再續~await與async引起的w3wp.exe崩潰-問題友好的解決

張佔嶺發表於2016-05-11

返回目錄

關於死鎖的原因

理解該死鎖的原因在於理解await 處理contexts的方式,預設的,當一個未完成的Task 被await的時候,當前的上下文將在該Task完成的時候重新獲得並繼續執行剩餘的程式碼。這個context就是當前的SynchronizationContext 除非它是空的。WEB應用程式的SynchronizationContext 有排他性,只允許一個執行緒執行。當await 完成的時候,它試圖在它原來的程式碼上下文執行它剩餘的部分,但是該程式碼上下文中已經有一個執行緒在了,就是那個一直在同步等待async 完成的那個執行緒,它們兩個相互等待,因此就死鎖了。

MSDN使用非同步應該注意

規則
描述
例外
避免使用 async void
優先使用 async Task 而不用 async void
Event handlers
Async到頂
不要混合使用 blocking 和 async 的程式碼
Console main method
注意配置好執行的context
儘量設定 ConfigureAwait(false)
需要context的除外

 

感恩的心

多謝網上很多文章的分享,在相關文章中找到了在同步程式碼中使用非同步程式碼的無阻塞方案,之前也自己寫了幾個測試的DEMO,但Task<T>這種帶有返回值的非同步方法還是會出現死鎖,之前程式碼如下:

    /// <summary>
    /// 大叔測試
    /// </summary>
    public class tools
    {
        #region 假設這些方法被第三方被封裝的,不可修改的方法
        public static async Task TestAsync()
        {
            await Task.Delay(1000)
                      .ConfigureAwait(false);//不會死鎖
        }

        public static async Task<string> GetStrAsync()
        {
            return await Task.Run(() => "OK").ConfigureAwait(false);
        }

        public static async Task DelayTestAsync()
        {
            Logger.LoggerFactory.Instance.Logger_Info("DelayAsync");
            await Task.Delay(1000);
        }

        public static async Task<string> DelayGetStrAsync()
        {
            return await Task.Run(() => "OK");
        }
        #endregion

        #region 我們需要在自己程式碼中封裝它,解決執行緒死鎖
        /// <summary>
        /// 沒有返回值的同步呼叫非同步的實體
        /// </summary>
        /// <param name="func"></param>
        public static void ForWait(Func<Task> func)
        {
            func().ConfigureAwait(false);
        }
        /// <summary>
        /// 存在返回值的同步呼叫非同步的實體
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="func"></param>
        /// <returns></returns>
        public static T ForResult<T>(Func<Task<T>> func)
        {
            var a = func();
            a.ConfigureAwait(false);
            return a.Result;
        }
        #endregion
    }

對於上面的程式碼,當執行一個Task反回型別(即無返回結果)時,程式是沒有問題的,但可以存在返回結果,那麼上面的ForResult方法依舊會產生死鎖!執著的我當然不會就此罷休,找了一些文章後,終於還是有了結果,在對當前上下文和非同步上下文進行了簡

單的處理後,最終還是實現了同步與非同步的並存所以說,人是最聰明的一種動物,一切都皆有可能,只要你想!

Lind.DDD.Utils.AsyncTaskManager程式碼如下,希望可以給大家帶來一些啟發和幫助

 

  /// <summary>
    /// 非同步執行緒管理-在同步程式中呼叫非同步,解決了執行緒死鎖問題
    /// </summary>
    public class AsyncTaskManager
    {
        /// <summary>
        /// 執行無返回型別的非同步方法
        /// </summary>
        /// <param name="task"></param>
        public static void RunSync(Func<Task> task)
        {
            var oldContext = SynchronizationContext.Current;//同步上下文 
            var synch = new ExclusiveSynchronizationContext();//非同步上下文
            SynchronizationContext.SetSynchronizationContext(synch);//設定當前同步上下文
            synch.Post(async obj =>
            {
                try
                {
                    await task();
                }
                catch (Exception e)
                {
                    synch.InnerException = e;
                    throw;
                }
                finally
                {
                    synch.EndMessageLoop();
                }
            }, null);
            synch.BeginMessageLoop();
            SynchronizationContext.SetSynchronizationContext(oldContext);
        }
        /// <summary>
        /// 執行返回型別為T的非同步方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="task"></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 obj =>
            {
                try
                {
                    ret = await task();
                }
                catch (Exception e)
                {
                    synch.InnerException = e;
                    throw;
                }
                finally
                {
                    synch.EndMessageLoop();
                }
            }, null);
            synch.BeginMessageLoop();
            SynchronizationContext.SetSynchronizationContext(oldContext);
            return ret;
        }

        /// <summary>
        /// 非同步上下文物件
        /// </summary>
        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");
            }
            /// <summary>
            /// 新增到非同步佇列
            /// </summary>
            /// <param name="d"></param>
            /// <param name="state"></param>
            public override void Post(SendOrPostCallback d, object state)
            {
                lock (items)
                {
                    items.Enqueue(Tuple.Create(d, state));
                }
                workItemsWaiting.Set();
            }
            /// <summary>
            /// 非同步結束
            /// </summary>
            public void EndMessageLoop()
            {
                Post(obj => done = true, null);
            }
            /// <summary>
            /// 處理非同步佇列中的訊息
            /// </summary>
            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("AsyncInline.Run method threw an exception.",
                             InnerException);
                        }
                    }
                    else
                    {
                        workItemsWaiting.WaitOne();
                    }
                }
            }
            public override SynchronizationContext CreateCopy()
            {
                return this;
            }
        }
    }

最後我們進行測試中,看到執行緒沒有出現死鎖問題!

感謝各位的閱讀!

返回目錄

相關文章