理解該死鎖的原因在於理解await 處理contexts的方式,默認的,當一個未完成的Task 被await的時候,當前的上下文將在該Task完成的時候重新獲得並繼續執行剩余的代碼。這個context就是當前的SynchronizationContext ,除非它是空的。WEB應用程序的SynchronizationContext 有排他性,只允許一個線程運行。當await 完成的時候,它試圖在它原來的代碼上下文執行它剩余的部分,但是該代碼上下文中已經有一個線程在了,就是那個一直在同步等待async 完成的那個線程,它們兩個相互等待,因此就死鎖了。
規則
描述
例外
避免使用 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;
}
}
}
最後我們進行測試中,看到線程沒有出現死鎖問題!
C#~異步編程再續~await與async引起的w3wp.exe崩潰
C#~異步編程再續~async異步方法與同步方法的並行
基礎才是重中之重~多線程的代價~我的內存都被吃了!
EF架構~EF異步改造之路~倉儲接口的改造~續
EF架構~EF異步改造之路~讓DbContextRepository去實現異步接口
EF架構~EF異步改造之路~倉儲接口的改造
C#~異步編程續~.net4.5主推的await&async應用
C#~異步編程
感謝各位的閱讀!