I have a WPF application that executes external programs to process media files, and so that the GUI doesn't freeze when the media files are being processed, I execute the process on a separate thread through backgroundworker.
private void BackgroundWorkerExecProcess(Process process)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = false;
worker.DoWork += DoWork;
worker.RunWorkerCompleted += WorkerCompleted;
worker.RunWorkerAsync(process);
}
void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
Process process = e.Argument as Process;
process.Start();
string stderr = process.StandardError.ReadToEnd();
//I want to display stderr on main thread
process.WaitForExit();
}
void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//some code to update gui telling user that process has finished
}
so, if there is something printed to stderr, I can see it in the debugger, but if I try to do anything with the string stderr, such as if I have a textbox called "_tbLog" and did
_tbLog.Text+=stderr;
I get an error from the compiler about them being on separate threads. is there a way to pass the object from the worker threa开发者_开发问答d to the main thread?
In DoWork, set e.Result to your object. In the WorkerCompleted you can get that object back out... it will once again be e.Result of type object. Just cast it to the object it was. The WorkerCompleted should be on the correct thread.
Here is one of mine:
private void workerUpdateBuildHistory_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
UpdateStatusModel model = (UpdateStatusModel)e.Argument;
BuildService buildService = new BuildService(model.TFSUrl);
e.Result = buildService.UpdateBuildHistoryList(model);
}
private void workerUpdateBuildHistory_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
BuildHistoryListModel model = (BuildHistoryListModel)e.Result;
if (model != null)
{
listViewPastBuilds.Items.Clear();
foreach (var item in model.Builds)
{
listViewPastBuilds.Items.Add(item);
}
}
}
Use your WorkerCompleted event handler to make changes the UI, it runs on the right thread. All you have to do is pass the string to the event handler. Which is what DoWorkEventArgs.Result was designed to do. You'll retrieve it in the event handler from e.Result. Thus:
void DoWork(object sender, DoWorkEventArgs e)
{
//...
e.Result = stderr;
}
void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null) DisplayError(e.Error);
else _tbLog.Text += (string)e.Result;
}
First you need to place whatever result object (in this example, a list of strings) in the DoWorkEventArgs.Result property, then retrieve this object via the RunWorkerCompletedArgs.Result property
Then, hook up an event handler RunWorkedCompleted event of the Background worker and have it pass back whatever object you want in the RunWorkerCompletedEventArgs.Result property.
Example:
void DoWork(object sender, DoWorkEventArgs arg)
{
List<string> results = new List<string>();
results.Add("one");
results.Add("two");
results.Add("three");
arg.Results = results;
}
void WorkComplete(object sender, runWorkerCompelteEventArgs arg)
{
//Get our result back as a list of strings
List<string> results = (List<string>)arg.Result;
PrintResults(results);
}
Note: I have not tested this code, but I believe it should compile. http://msdn.microsoft.com/en-us/library/system.componentmodel.runworkercompletedeventargs.result.aspx http://msdn.microsoft.com/en-us/library/system.componentmodel.doworkeventargs.aspx
you can also use the dispatcher as @Zembi mentiones:
this.Dispatcher.Invoke( new Action( () => {
_tbLog.Text+=stderr;
} ) );
you can also use TPL to make sure things get run on the right thread
-edit-
Here is a good article on diffrent ways to do ui updates, including using TPL
精彩评论