Thread(執行緒)四
今天繼續講講執行緒的異常處理、執行緒取消、多執行緒的臨時變數和執行緒安全lock的問題。
1、非同步處理。
一般來說如果是同步方法的非同步處理,我們大多都是try catch住,但是非同步方法應該怎麼做呢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#region 異常處理
//多執行緒的委託是不允許異常的, try catch包住,寫下日誌
for
(
int
i = 0; i < 20; i++)
{
string
name =
string
.Format(
"btnThreadCore_Click{0}"
, i);
Action<
object
> act = t =>
{
try
{
Thread.Sleep(2000);
if
(t.ToString().Equals(
"btnThreadCore_Click11"
))
{
throw
new
Exception(
string
.Format(
"{0} 執行失敗"
, t));
}
if
(t.ToString().Equals(
"btnThreadCore_Click12"
))
{
throw
new
Exception(
string
.Format(
"{0} 執行失敗"
, t));
}
Console.WriteLine(
"{0} 執行成功"
, t);
}
catch
(Exception ex)
{
Console.WriteLine(ex.Message);
}
};
taskList.Add(taskFactory.StartNew(act, name));
}
Task.WaitAll(taskList.ToArray());
#endregion
|
2、執行緒取消。
Task不能主動取消,就好比向CPU發起了一個請求,但是你中途想中斷這個請求,在正常情況下是做不到的,
同樣,執行緒也做不到這一點,只有通過檢測訊號量的方式,來檢測,使其執行緒本身來做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
CancellationTokenSource cts =
new
CancellationTokenSource();
for
(
int
i = 0; i < 40; i++)
{
string
name =
string
.Format(
"btnThreadCore_Click{0}"
, i);
Action<
object
> act = t =>
{
try
{
Thread.Sleep(2000);
if
(t.ToString().Equals(
"btnThreadCore_Click11"
))
{
throw
new
Exception(
string
.Format(
"{0} 執行失敗"
, t));
}
if
(t.ToString().Equals(
"btnThreadCore_Click12"
))
{
throw
new
Exception(
string
.Format(
"{0} 執行失敗"
, t));
}
if
(cts.IsCancellationRequested)
{
Console.WriteLine(
"{0} 放棄執行"
, t);
}
else
{
Console.WriteLine(
"{0} 執行成功"
, t);
}
}
catch
(Exception ex)
{
cts.Cancel();
Console.WriteLine(ex.Message);
}
};
taskList.Add(taskFactory.StartNew(act, name);
//沒有啟動的任務 在Cancel後放棄啟動
}
Task.WaitAll(taskList.ToArray());
|
通過程式碼執行可以看到會出現三種結果,那麼這三種結果是什麼情況下出現的呢,
執行成功和執行失敗這兩種情況應該好理解,,放棄執行是在執行失敗出現時,捕獲住了異常資訊,然後通過cts.Cancel();使訊號量改變,
然後通過cts.IsCancellationRequested判斷,這就出現了只要是出現了執行失敗,後面都是放棄執行的情況。
3、多執行緒臨時變數
1 2 3 4 5 6 7 8 9 10 |
for
(
int
i = 0; i < 5; i++)
{
new
Action(() =>
{
//Thread.Sleep(100);
Console.WriteLine(i);
}).BeginInvoke(
null
,
null
);
}
|
執行這麼一段關鍵程式碼,會出現什麼樣的結果呢。
出現5個5,這是為什麼呢,怎麼和我們想的不一樣,按理說不應該是出現0、1、2、3、4這樣的數?
這裡因為for迴圈是一定會比執行緒呼叫快,每一遍迴圈完,只是提交了執行緒,還沒有呼叫,當呼叫時,迴圈已經結束,額呼叫時只會取最後i的值。
這就會出現5個5的情況,那麼如何才能出現我們想要的結果呢。
1 2 3 4 5 6 7 8 9 10 |
for
(
int
i = 0; i < 5; i++)
{
int
k = i;
new
Action(() =>
{
Thread.Sleep(100);
// Console.WriteLine(i);
Console.WriteLine(k);
}).BeginInvoke(
null
,
null
);
}
|
只需要在迴圈體中加一個變數儲存i的值,就可以了。
4、執行緒安全 lock
關於執行緒安全,有的人太過於重視,而也有的人一點也不關心。那麼我們應該怎麼做執行緒安全呢。
1 2 3 4 5 6 7 8 9 10 11 |
private
int
TotalCount = 0; <br>
for
(
int
i = 0; i < 10000; i++)
{
taskList.Add(taskFactory.StartNew(() =>
{
this
.TotalCount += 1;
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(
this
.TotalCount);
|
先來看看這段程式碼,可能大多數人會認為結果會是10000這樣的結果,但是事實呢
你會發現,第一次是9997,第二次9998,第三次是9996,沒有一次出現我們想要的結果。這又是為什麼,
因為我們宣告的private int TotalCount = 0,是共有變數,所有的執行緒都是呼叫同一個,這就出現了執行緒安全的問題。
那麼我們應該如何解決這種情況呢,這就要加一把鎖。
private static object btnThreadCore_Click_Lock = new object();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<em id=
"__mceDel"
>
for
(
int
i = 0; i < 10000; i++)
{
taskList.Add(taskFactory.StartNew(() =>
{
lock
(btnThreadCore_Click_Lock)
{
this
.TotalCount += 1;
}
}));
}
Task.WaitAll(taskList.ToArray());
Console.WriteLine(
this
.TotalCount);
</em>
|
這樣在執行相加的時候只會允許一個執行緒進行相加。
講完這四點,再來說說Await/Async,這兩個一般都是同時出現
1、只出現Async,會出現一個警告,合普通執行緒沒什麼區別。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private
static
async
void
NoReturnNoAwait()
{
//主執行緒執行
Console.WriteLine(
"NoReturnNoAwait Sleep before Task,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
Task task = Task.Run(() =>
//啟動新執行緒完成任務
{
Console.WriteLine(
"NoReturnNoAwait Sleep before,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(3000);
Console.WriteLine(
"NoReturnNoAwait Sleep after,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
});
//主執行緒執行
Console.WriteLine(
"NoReturnNoAwait Sleep after Task,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
}
|
同時出現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
private
static
async
void
NoReturn()
{
//主執行緒執行
Console.WriteLine(
"NoReturn Sleep before await,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
TaskFactory taskFactory =
new
TaskFactory();
Task task = taskFactory.StartNew(() =>
{
Console.WriteLine(
"NoReturn Sleep before,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(3000);
Console.WriteLine(
"NoReturn Sleep after,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
});
await task;
//子執行緒執行 其實是封裝成委託,在task之後成為回撥(編譯器功能 狀態機實現)
Console.WriteLine(
"NoReturn Sleep after await,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
}
|
帶有await時,後面執行時,會發現也是子執行緒在執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private
static
async Task NoReturnTask()
{
//這裡還是主執行緒的id
Console.WriteLine(
"NoReturnTask Sleep before await,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
Task task = Task.Run(() =>
{
Console.WriteLine(
"NoReturnTask Sleep before,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(3000);
Console.WriteLine(
"NoReturnTask Sleep after,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
});
await task;
Console.WriteLine(
"NoReturnTask Sleep after await,ThreadId={0}"
, Thread.CurrentThread.ManagedThreadId);
|