非同步陷阱之死結篇

來源:互聯網
上載者:User

標籤:spreadjs   wijmo5   非同步   死結   

??

提倡非同步編程旨在給使用者更好的前端體驗,但非同步編程也讓學習成本和犯錯幾率大大升高,其中最常見且最難處理的就是死結。

何謂“死結”,英文術語稱“Deadlock”,當兩個以上的運算單元,雙方都在等待對方停止運行,以取得系統資源,但是沒有一方提前退出時,這種狀況,就稱為死結。?

舉個例子吧,這裡是一段經典的死結範例程式碼:

int sharedResource1 = 1, sharedResource2 = 2;var lockResource1 = newobject();var lockResource2 = newobject();var t1 = newThread(() =>{    Console.WriteLine("thead 1 begin");    lock (lockResource1)    {        Thread.Sleep(10);        lock (lockResource2)        {            sharedResource1++;            sharedResource2++;        }    }    Console.WriteLine("thead 1 end");});var t2 = newThread(() =>{    Console.WriteLine("thead 2 begin");    lock (lockResource2)    {        Thread.Sleep(10);?        lock (lockResource1)        {            sharedResource1++;            sharedResource2++;        }    }    Console.WriteLine("thead 2 end");});t1.Start();t2.Start();

運行結果如下,永遠也不會看到“thread x end”:
?
這是一個不同次序請求加鎖導致死結,歸功於我們的教材對此類死結的解釋非常詳細,這裡我一筆帶過,接下來看看日常開發中經常遇到的一些更具體的死結情況——線程死結。

情境1—Task之間互相等待導致死結:

Task t1 = null, t2 = null;t1 = Task.Factory.StartNew(() =>{    Console.WriteLine("task 1 begin");    Task.Delay(10);    Task.WaitAll(t2);    Console.WriteLine("task 1 end");});t2 = Task.Factory.StartNew(() =>{    Console.WriteLine("task 2 begin");    Task.Delay(10);    Task.WaitAll(t1);    Console.WriteLine("task 2 end");});Task.WaitAll(t1, t2);Console.WriteLine("Done");

情境2—?WinForm Invoke搶奪UI線程死結:

privatevoid button1_Click(object sender, EventArgs e){    var t = Task.Factory.StartNew<string>(() =>        {            Thread.Sleep(0);            var text = Invoke(newFunc<string>(() =>            {                // do some ui-dependent works                return Text;            }));?            return text + " - new title";        });    Text = t.Result;}

情境3—WPF Dispatcher切換死結

privatevoid Button_Click(object sender, RoutedEventArgs e){    var t = Task.Factory.StartNew<Brush>((state) =>    {        Task.Delay(10);        var clr = (Color)newColorConverter()            .ConvertFromInvariantString(state asstring);        var brush = Dispatcher.Invoke<SolidColorBrush>(() =>            {                // do some works                returnnewSolidColorBrush() { Color = clr };            });        return brush;    }, "red");    theButton.Background = t.Result;}

這裡將各種無關代碼精簡篩除,基本上很快就可以發現這些情況中的問題,是的,實際上以上幾種情境均是同一個原因——wait線程鎖:主執行線程調用子線程後掛起等待子線程結果,子線程又需要切換到主線程或者等待主線程返回,從而導致兩個線程均處在阻塞狀態(死結),如所示:

 
?

解決方案很簡單,去除所有的同步等待,至少確保在主線程上一定不要使用同步等待,如何操作呢?你可以到多種選擇,這裡我提幾點,拋磚引玉,希望大家可以在實際應用中或者更多靈感和解決方案。

1、去除所有wait,使用async和await關鍵字重寫,推薦使用。
這裡或許你會有些迷惑,為什麼async和await就能保證不會線程死結呢??如示意程式碼片段,當前線程執行完(1)之後,接著執行(2),注意這裡執行(2)會切換線程,但是不是阻塞當前線程,.NET在這裡耍了個“花招”,實際編譯器發現async和await關鍵字的時候會自動插入一些代碼,利用狀態機器在(3)的位置做了個標記,讓當前線程“飛”了一會,等到await所處的子線程結束的時候,修改狀態機器狀態,讓當前線程恢複到(3)這裡,接著就可以跑(4),從開發人員的角度來看,好像這一段代碼是順序執行的。重要的是,這裡沒有wait鎖。

2、去除所有wait,使用Task.ContinueWith來實現代碼順序。
var ta = new Task(()=>{ doSome(); });
ta.ContinueWith((tc)=>{ doAnother(tc.Result); });

3、去除所有wait,將wait之後的代碼移到單獨的調用中,使用事件或者回呼函數的方式,在子線程結束的時候,啟用主線程。以WinForm為例,如所示:
?

附上文中所提到測試的代碼工程:

非同步陷阱之死結篇

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.