您的位置:首页 > 其它

Multithreaded User Interfaces

2007-07-08 17:13 316 查看
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)


http://dl2.csdn.net/down4/20070708/08171745159.jpg"
border="0" >


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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: