Multithreaded User Interfaces
来源:互联网 发布:计算机教学数据 编辑:程序博客网 时间:2024/05/17 03:20
1. Issues of a single-threaded application:
- Cannot response to UI requests in client area until long-running working function is returned.
- User cannot see the progress until long-running working function is returned.
2. To avoid these issues, this application needs a way to free the UI thread to do UI work and handle the long-running function in the background. For this, it needs another thread of execution. [Asynchronous Operations] Use the BackgroundWorker component from the System.ComponentModel namespace.
3. How to implement safe, asynchronous, long-running operations with progress reports in WinForm[Multi-threaded application]:
Tips:
a) Direct manipulation of controls from the worker thread is forbidden, which means UI controls cannot call from worker thread. we can pass object argument or use shared data.
b)
- Initiating a Worker Thread:
calling BackgroundWorker's RunWorkerAsync method:
void calcButton_Click(object sender, EventArgs e) {
...
// Initiate asynchronous pi calculation on worker thread this.backgroundWorker.RunWorkerAsync(
(int)this.decimalPlacesNumericUpDown.Value);
- Executing from the Worker Thread:
DoWork is BackgroundWorker's default event which let you handle to process your long-running operation on a worker thread from the thread pool.
System.ComponentModel.BackgroundWorker backgroundWorker;
...
void InitializeComponent() {
...
this.backgroundWorker = new System.ComponentModel.BackgroundWorker();
...
// backgroundWorker
this.backgroundWorker.DoWork += this.backgroundWorker_DoWork;
...
}
// AsyncCalcPiForm.cs
partial class AsyncCalcPiForm : Form {
...
// Executed on a worker thread from the thread pool
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
...
}
}
How to pass the object from UI to worker thread
partial class AsyncCalcPiForm : Form {
...
void calcButton_Click(object sender, EventArgs e) {
...
// Begin calculating pi asynchronously
this.backgroundWorker.RunWorkerAsync(
(int)this.decimalPlacesNumericUpDown.Value);
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
CalcPi((int)e.Argument);
}
- Reporting Progress
Reporting progress from the worker thread back to the UI thread.
1st, set WorkerReportsProgress property to true.
2nd, to report progress from the worker thread, you call the BackgroundWorker object's ReportProgress method. To pass an object from worker thread to UI…
class AsyncCalcPiForm : Form {
...
class CalcPiUserState {
public readonly string Pi;
public readonly int TotalDigits;
public readonly int DigitsSoFar;
public CalcPiUserState(
string pi, int totalDigits, int digitsSoFar) {
this.Pi = pi;
this.TotalDigits = totalDigits;
this.DigitsSoFar = digitsSoFar;
}
}
void CalcPi(int digits) {
StringBuilder pi = new StringBuilder("3", digits + 2);
// Report initial progress. use obeject argument, not shared data
this.backgroundWorker.ReportProgress(0,
new CalcPiUserState(pi.ToString(), digits, 0));
if( digits > 0 ) {
pi.Append(".");
for( int i = 0; i < digits; i += 9 ) {
...
// Report continuing progress
this.backgroundWorker.ReportProgress(0,
new CalcPiUserState(pi.ToString(), digits, i + digitCount));
}
}
}
}
3rd, UI can access them and respond accordingly by handling BackgroundWorker's ProgressChanged event.
// AsyncCalcPiForm.Designer.cs
partial class AsyncCalcPiForm {
...
System.ComponentModel.BackgroundWorker backgroundWorker;
...
void InitializeComponent() {
...
this.backgroundWorker =
new System.ComponentModel.BackgroundWorker();
...
// backgroundWorker
this.backgroundWorker.ProgressChanged +=
backgroundWorker_ProgressChanged;
...
}
}
// AsyncCalcPiForm.cs
partial class AsyncCalcPiForm : Form {
...
void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
...
}
void backgroundWorker_ProgressChanged(
object sender, ProgressChangedEventArgs e) {
// Show progress
CalcPiUserState progress = (CalcPiUserState)e.UserState;
ShowProgress(
progress.Pi, progress.TotalDigits, progress.DigitsSoFar);
}
}
- Completion
When a BackgroundWorker-managed worker thread completes, BackgroundWorker fires the RunWorkerCompleted event. This allows us to refactor our ShowProgress method and let RunWorkerCompleted reset the status strip progress bar state:
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
// Track start time
DateTime start = DateTime.Now;
CalcPi((int)e.Argument);
// Return elapsed time
DateTime end = DateTime.Now;
TimeSpan elapsed = end - start;
e.Result = elapsed;
}
void backgroundWorker_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e) {
// Was there an error?
if( e.Error != null ) {
this.resultsTextBox.Text = e.Error.Message;
return;
}
...
// Show elapsed time
TimeSpan elapsed = (TimeSpan)e.Result;
MessageBox.Show("Elapsed: " + elapsed.ToString());
// Reset progress UI
this.calcToolStripStatusLabel.Text = "Ready";
this.calcToolStripProgressBar.Visible = false;
}
- Cancellation
We use the CancellationPending property of BackgroundWorker to find out whether we've already canceled the pi calculation. CancelAsync is actually only a request, so the worker thread needs to watch for it by checking the BackgroundWorker component's CancellationPending property.
void calcButton_Click(object sender, EventArgs e) {
// Don't process if cancel request pending
// (Should not be called, because we disabled the button...)
if( this.backgroundWorker.CancellationPending ) return;
// If worker thread currently executing, cancel it
if( this.backgroundWorker.IsBusy ) {
this.calcButton.Enabled = false;
this.backgroundWorker.CancelAsync();
return;
}
// Set calculating UI
this.calcButton.Text = "Cancel";
this.calcToolStripProgressBar.Visible = true;
this.calcToolStripStatusLabel.Text = "Calculating...";
// Begin calculating pi asynchronously
this.backgroundWorker.RunWorkerAsync(
(int)this.decimalPlacesNumericUpDown.Value);
}
void CalcPi(int digits) {
StringBuilder pi = new StringBuilder("3", digits + 2);
// Report initial progress
this.backgroundWorker.ReportProgress(0,
new CalcPiUserState(pi.ToString(), digits, 0));
if( digits > 0 ) {
pi.Append(".");
for( int i = 0; i < digits; i += 9 ) {
...
// Report continuing progress
this.backgroundWorker.ReportProgress(0,
new CalcPiUserState(pi.ToString(), digits, i + digitCount));
// Check for cancellation
if( this.backgroundWorker.CancellationPending ) return;
}
}
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
...
CalcPi((int)e.Argument);
// Indicate cancellation
if( this.backgroundWorker.CancellationPending ) {
e.Cancel = true;
}
...
}
void backgroundWorker_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e) {
...
// Was the worker thread canceled?
if( e.Cancelled ) {
this.resultsTextBox.Text = "Canceled";
return;
}
...
}
- Shared Data
By passing copies or ownership of data around, we ensure that no two threads need to share access to any one piece of data. We have another choice: suppose we decide that we prefer shared access to an object. For proper concurrent access to shared data, you must synchronize access to the data that is, make sure that one thread waits patiently while another thread works on the data. To synchronize access to shared data, C# provides the lock block:
Shared access to data between threads makes it very easy to get into race conditions, in which one thread is racing to read data that is only partially up-to-date before another thread has finished updating it.
SharedCalcPiUserState state = new SharedCalcPiUserState();
object stateLock = new object();
void CalcPi(int digits) {
...
// Synchronize access to shared data
// on the worker thread
lock( stateLock ) {
this.state.Pi = pi.ToString();
this.state.TotalDigits = digits;
this.state.DigitsSoFar = i + digitCount;
this.backgroundWorker.ReportProgress(0);
}
...
}
void backgroundWorker_ProgressChanged(
object sender, ProgressChangedEventArgs e) {
// Synchronize access to shared data
// on the UI thread
lock( stateLock ) {
ShowProgress(
this.state.Pi, this.state.TotalDigits, this.state.DigitsSoFar);
}
}
Now that your data has been properly protected against race conditions, you must watch out for another problem known as a deadlock. A deadlock occurs when each of two threads has locked a resource and both subsequently wait for the resource held by the other thread, causing each thread to stop dead, waiting forever. When two threads are deadlocked, each of them waits for the other to complete its work before continuing, thereby ensuring that neither actually progresses.
Multithreaded programming with shared data is hard. By passing copies or ownership of data around, we ensure that no two threads need to share access to any one piece of data. If you don't have shared data, there's no need to synchronize access to it. But if you find that you need access to shared datamaybe because the overhead of copying the data is too great a burden in space or timethen you need to read up on multithreading and shared data synchronization, topics that are beyond the scope of this book.
- Multithreaded User Interfaces
- Professional Java User Interfaces
- Graphics User Interfaces
- Developing User Interfaces
- Kernel Space - User Space Interfaces
- Best Practices for User Interfaces
- Kernel Space - User Space Interfaces
- [Exercises]24 Graphical User Interfaces
- DirectX9 User Interfaces : Design and Implementation
- Find Missing Component Interfaces for user
- Customize User Interfaces and Pass User Input to Installer Classes
- Interfaces
- Interfaces
- interfaces
- Building Graphical User Interfaces with the MVC Pattern (ZZ)
- PLEAC-Perl 教程 - User Interfaces (Perl进阶者极力推荐)
- Seeing Data : Designing User Interfaces for Database Systems Using .NET
- Building User Interfaces by Using Windows® Presentation Foundation
- 与世界顶级公关公司对话笔记
- 突然很想陪她睡
- 各种考试改革
- 在Linux下安装JDK
- 电脑前的身体保健攻略
- Multithreaded User Interfaces
- 要回家了!
- Three Methods to Parallel Programming Decomposition
- 与技术无关
- 我的技术博客开张了
- 肉鸡上制作隐藏网站
- 转载:介绍一个小插件,可以很方便的创建包含.net framework 1.1或者MDAC2.7的部署项目--[Bootstrapper]
- the first blog~
- IT开发工程师的悲哀