原文:http://www.dotblogs.com.tw/dora0825/archive/2012/12/27/86024.aspx
前言:
最新的WP8 SDK使用WinRT library,和windows 8 APP一樣,更加方便開發者了。
這次要來學習的KeyPoint !
1.利用await關鍵字暫停正在執行的方法,直到呼叫async。避免霸佔UI thread影響使用者體驗。
2.讓非同步Code表現像同步,不會有停止的感覺:)
3.實現 long-running tasks在背景執行!
Demo 1 –Async and Await
此範例示範 同步呼叫task、使用await進行非同步呼叫task、不使用await進行非同步呼叫task三種狀況,手機UI thread是否有反應。
首先創造一個long-running tasks 需要霸佔thread 3秒。
1. 同步呼叫
12345678 |
private
void
CallSync_Click
(
object
sender
,
RoutedEventArgs
e
)
{
Debug
.
WriteLine
(
"=============== Call synchronously ==================="
);
Debug
.
WriteLine
(
"同步呼叫,執行ALongRunningOperation()的執行緒的ID: "
+
Thread
.
CurrentThread
.
ManagedThreadId
);
int
returnValue
=
ALongRunningOperation
();
Debug
.
WriteLine
(
"回傳值: "
+
returnValue
);
Debug
.
WriteLine
(
"繼續執行UI thread"
);
}
|
註記:
按下同步呼叫按鈕後,執行ALongRunningOperation(),因為thread被霸佔了,所以此時UI thread是被block住的,此時按下press Me! button,不會有任何反應。
2. 使用await進行非同步呼叫task
thread在背景執行ALongRunningOperation(),回傳值尚未回來,但UI thread並未被block住,所以此時按press Me! button是可以反應變色的:D
這是因為 TPL (Task Parallel Library) 允許我們對同步的方法們,執行非同步的呼叫。
註記:
所以範例建立一個Task<int>方法去呼叫回傳值為int的ALongRunningOperation()。
12345678910 |
private
async
Task
<
int
>
ALongRunningOperationAsync
()
{
int
returnValue
=
0
;
// Queue up the work to run on the Threadpool
await
Task
.
Run
(()=>
{
returnValue
=
ALongRunningOperation
();
});
return
returnValue
;
}
|
12345678 |
private
async
void
CallAsync_Click
(
object
sender
,
RoutedEventArgs
e
)
{
Debug
.
WriteLine
(
"=============== 使用await非同步呼叫 ==================="
);
Debug
.
WriteLine
(
"使用await,執行ALongRunningOperation()的執行緒的ID: "
+
Thread
.
CurrentThread
.
ManagedThreadId
);
int
returnValue
=
await
ALongRunningOperationAsync
();
Debug
.
WriteLine
(
"回傳值: "
+
returnValue
);
Debug
.
WriteLine
(
"繼續執行UI thread"
);
}
|
3. 不使用await進行非同步呼叫task
123456789 |
private
void
CallAsyncNoAwait_Click
(
object
sender
,
RoutedEventArgs
e
)
{
Debug
.
WriteLine
(
"===============不使用await非同步呼叫 ==================="
);
Debug
.
WriteLine
(
"不使用await,執行ALongRunningOperation()的執行緒的ID: "
+
Thread
.
CurrentThread
.
ManagedThreadId
);
//int returnValue =
ALongRunningOperationAsync
();
//Debug.WriteLine("回傳值: " + returnValue);
Debug
.
WriteLine
(
"繼續執行UI thread"
);
}
|
註記:
不使用await呼叫async方法是可以的,在我們不使用方法回傳值的時候,在此範例中output就不會等待async方法完成依然執行UI thread。但如果需要等待async方法的回傳值並且使用,則會產生錯誤。
結論:
此範例示範了同步跟非同步方法的呼叫方法以及使用await關鍵字去操作task在thread進行的順序,WinRT API提供開發者方便快速的方式去提升使用者體驗感,不會讓使用者覺得程式UI沒有反應,而感到困惑 :-)
Demo 2 – BackgroundWorker vs Task
如果有long-running工作想在背景執行,在WP8中有兩種方法可以達成。
1. 執行背景工作
BackgroundWorker。
12345678910111213141516171819 |
private
void
LaunchBGWButton_Click
(
object
sender
,
RoutedEventArgs
e
)
{
Debug
.
WriteLine
(
"(BGW)呼叫者執行緒ID: "
+
Thread
.
CurrentThread
.
ManagedThreadId
);
BackgroundWorker
bgw
=
new
BackgroundWorker
();
bgw
.
RunWorkerCompleted
+=
((
s
,
a
)
=>
MessageBox
.
Show
(
"BGWButton----工作已完成, 結果: "
+
(
int
)
a
.
Result
)
);
bgw
.
DoWork
+=
((
s
,
a
)
=>
{
Debug
.
WriteLine
(
"(BGW)背景工作者執行緒ID: "
+
Thread
.
CurrentThread
.
ManagedThreadId
);
// Simulate some long running work
Thread
.
Sleep
(
5000
);
// Return the result
a
.
Result
=
1234
;
});
// Now start execution
bgw
.
RunWorkerAsync
();
}
|
註記:
BackgroundWorker.DoWork 事件
Task比BackgroundWorker更為簡潔整齊
12345678910111213141516 |
private
async
void
LaunchTaskButton_Click
(
object
sender
,
RoutedEventArgs
e
)
{
Debug
.
WriteLine
(
"(Task)呼叫者執行緒ID: "
+
Thread
.
CurrentThread
.
ManagedThreadId
);
int
result
=
await
Task
.
Factory
.
StartNew
<
int
>(()
=>
{
Debug
.
WriteLine
(
"(Task)背景工作者執行緒ID: "
+
Thread
.
CurrentThread
.
ManagedThreadId
);
// Simulate some long running work
Thread
.
Sleep
(
5000
);
// Return the result
return
4321
;
}
);
MessageBox
.
Show
(
"TaskButton----工作已完成, 結果: "
+
result
);
}
|
註記:
TaskFactory.StartNew
2.有進度回報的背景工作
BackgroundWorker支援Progress reports。
12345678910111213141516171819202122232425 |
private
void
LaunchBGWwithProgressButton_Click
(
object
sender
,
RoutedEventArgs
e
)
{
...
bgw
.
WorkerReportsProgress
=
true
;
...
bgw
.
ProgressChanged
+=
((
s
,
a
)
=>
{
// Progress Indicator value must be between 0 and 1
SystemTray
.
GetProgressIndicator
(
this
).
Value
=
(
double
)
a
.
ProgressPercentage
/
100.0
;
}
);
bgw
.
DoWork
+=
((
s
,
a
)
=>
{
// 模擬一個long running work
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
Thread
.
Sleep
(
500
);
// Report progress as percentage completed
bgw
.
ReportProgress
(
i
*
10
);
}
...
});
...
}
|
註記:
(1)設定BackgroundWorker的WorkerReportsProgress為true。
(2)針對ProgressChanged event 做處理。
(3)在BackgroundWorker的DoWork中呼叫BackgroundWorker.ReportProgress方法拿取進度百分比。
Task
123456789101112131415161718192021222324 |
private
async
void
LaunchTaskwithProgressButton_Click
(
object
sender
,
RoutedEventArgs
e
)
{
ShowandClearProgressIndicator
();
IProgress
<
int
>
progressReporter
=
new
Progress
<
int
>((
percentComplete
)
=>
{
// 進度指針數值範圍在0~1
SystemTray
.
GetProgressIndicator
(
this
).
Value
=
(
double
)
percentComplete
/
100.0
;
});
int
result
=
await
Task
.
Factory
.
StartNew
<
int
>(()
=>
{
// Simulate some long running work
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
Thread
.
Sleep
(
500
);
// Report progress as percentage completed
progressReporter
.
Report
(
i
*
10
);
}
// Return the result
return
4321
;
}
);
...
}
|
註記:
因為Task沒有支援Progress reports,所以在此範例Task code並不會比較簡潔。
3.消除背景工作
BackgroundWorker支援Cancellation。
1234567891011121314151617181920212223 |
private
void
LaunchBGWwithCancelButton_Click
(
object
sender
,
RoutedEventArgs
e
)
{
...
//在此bgwCancel是private
bgwCancel
.
WorkerSupportsCancellation
=
true
;
...
bgwCancel
.
DoWork
+=
((
s
,
a
)
=>
{
// 模擬一個long running work
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
...
// check是否取消背景工作?
if
(
bgwCancel
.
CancellationPending
)
{
a
.
Cancel
=
true
;
return
;
}
}
...
});
...
}
|
註記:
(1)設定BackgroundWorker的SupportsCancellation為true。
(2)在RunWorkerCompleted事件處理中檢查事件參數的Cancelled屬性確定工作是否完成或取消。
Task須透過OperationCanceledException。
1234567891011121314151617181920212223242526272829303132 |
private
async
void
LaunchTaskwithCancelButton_Click
(
object
sender
,
RoutedEventArgs
e
)
{
ShowandClearProgressIndicator
();
cancellationTokenSource
=
new
CancellationTokenSource
();
var
cancellationToken
=
cancellationTokenSource
.
Token
;
//自行定義進度回報的提供者
//間歇性的呼叫 Progress<T>.Report,回傳完成比例
...
try
{
int
result
=
await
Task
.
Factory
.
StartNew
<
int
>(()
=>
{
// 模擬一個long running work
for
(
int
i
=
0
;
i
<
10
;
i
++)
{
...
// check是否取消背景工作?
cancellationToken
.
ThrowIfCancellationRequested
();
}
...
},
cancellationToken
);
MessageBox
.
Show
(
"TaskwithCancelButton----工作已完成, 結果: "
+
result
);
}
catch
(
OperationCanceledException
ex
)
{
MessageBox
.
Show
(
"Task取消。"
);
}
SystemTray
.
GetProgressIndicator
(
this
).
IsVisible
=
false
;
}
|
註記:
(1)建立一個CancellationTokenSource,拿取其Token屬性當作Task.factory.StartNew的第二個參數。
(2)在外部呼叫想取消task時,事實上是呼叫CancellationTokenSource的Cancel method。(按下AppBar的取消鍵)
(3)在long running work中間歇性呼叫CancellaionToken.ThrowIfCancellationrequested() method,偵測是否有task取消的要求。
(4)task的取消要求會產生OperationCanceledException被caught的例外狀況。
註記:
執行task任務時,按下app bar的取消鍵,VS會停止認為是無法處理的例外,但只是背景執行緒無法處理,事實上會編排回呼叫者,並且丟回到原來的執行緒。
程式停止沒關係,按繼續執行(> 或是 F就可以了唷!
最後:
有關WP8 JumpStart詳細課程內容都可以上Windows Phone Developer Blog唷。
如有任何問題、錯誤,可在下面留言,謝謝您的指教。