C# - Background Workers?
I have a fairly complex program so I won't dump the whole thing in here. Here's a simplified version:
class Report {
private BackgroundWorker worker;
public Report(BackgroundWorker bgWorker, /* other variables, etc */) {
// other initializations, etc
worker = bgWorker;
}
private void SomeCalculations() {
// In this function, I'm doing things which may cause fatal errors.
// Example: I'm connecting to a database. If the connection fails,
// I need to quit and have my background worker report the error
}
}
// In the GUI WinForm app:
// using statements, etc.
using Report;
namespace ReportingService {
public partial class ReportingService : Form {
// My background worker
BackgroundWorker theWorker = new BackgroundWorker() {
WorkerReportsProgress = true
};
// The progress changed event
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e) {
// e.UserState and e.ProgressPercentage on some labels, etc.
}
// The do work event for the worker, runs the number crunching algorithms in SomeCalculations();
void worker_DoWork(object sender, DoWorkEventArgs e) {
Report aReport = e.Argument as Report;
aReport.SomeCalculations();
}
// The completed event, where all my trouble is. I don't know how to retrieve the error,
// or where it originates from.
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
// How, exactly, do I get this error message? Who provides it? How?
if (e.Error != null) {
MessageBox.Show("Error: " + (e.Error as Exception).ToString());
}
else if (e.Cancelled) {
MessageBox.Show("Canceled");
}
// operation succeeded
else {
MessageBox.Show("Success");
}
}
// Initialization of the forml, etc
public ReportingService() {
InitializeComponent();
theWorker.ProgressChanged += worker_ProgressChanged;
theWorker.DoWork += worker_DoWork;
theWorker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
// A button that the user clicks to execute the number crunching algorithm
private void sumButton_Click(object sender, EventArgs e) {
Report myReport = new Report(theWorker, /* some other variables, etc */)
theWorker.RunWorkerAsync(myReport);
}
}
}
Here's my logic, and please correct me if I'm going about this the wrong way:
I abstracted the class out of the GUI because it's ~2000 lines and needs to be it's own self contained object.
I pass the background worker into my class so that I can report back the progress of my number crunching.
What I don't know how to do is let the background worker know that an error has happened inside my class. In order to get the RunWorkerCompleted argument as an exception, where does my try/catch block need to go, and what should I do in the catch block?
Thanks for your help!
EDIT:
I've tried the following things to test the error handling:
Keep in mind I corrupted my database connection string to purposefully receive an error message.
In my class I do:
// My number crunching algorithm contained within my class calls a function which does this:
// try {
using (SqlConnection c = GetConnection()) { // note: I've corrupted the connection string on purpose
c.Open(); // I get the exception thrown here
using (SqlCommand queryCommand = new SqlCommand(query, c)) { /* Loop over query, etc. */ }
c.Close();
}
// } catch (Exception e) { }
1.
From my understanding, an unhandled exception gets cast to the Error
portion of the RunWorkerCompletedEventArgs
? When I try this I get the following:
// In my winform application I initialize my background worker with these events:
void gapBW_DoWork(object sender, DoWorkEventArgs e) {
Report aReport = e.Argument as Report;
Report.Initialize(); // takes ~1 minute, throws SQL exception
Report.GenerateData(); // takes around ~2 minutes, throws file IO exceptions
}
void gapBW_RunWorkerCompleted(object sender, RunWorkerComplete开发者_StackOverflow中文版dEventArgs e) {
if (e.Error != null) { // I can't get this to trigger, How does this error get set?
MessageBox.Show("Error: " + (e.Error as Exception).ToString());
}
else if (e.Cancelled) {
MessageBox.Show("Canceled: " + (e.Result).ToString());
}
else {
MessageBox.Show("Success");
}
}
Visual studio says that my application chokes on c.Open()
failing with an unhandled exception.
2. When I put a try/catch block in my DoWork function:
void gapBW_DoWork(object sender, DoWorkEventArgs e) {
try {
Report aReport = e.Argument as Report;
aReport.Initialize(); // throws SQL exceptions
aReport.GenerateData(); // throws IO file exceptions
}
catch (Exception except) {
e.Cancel = true;
e.Result = except.Message.ToString();
}
}
void gapBW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (e.Error != null) { // I can't get this to trigger, How does this error get set?
MessageBox.Show("Error: " + (e.Error as Exception).ToString());
}
else if (e.Cancelled) {
MessageBox.Show("Canceled: " + (e.Result).ToString());
}
else {
MessageBox.Show("Success");
}
}
I get a TargetInvocationException
was unhandled in Program.cs at the automatically generated Application.Run(new ReportingService());
line. I placed a breakpoint on my RunWorkerCompleted and can see that e.Cancelled = true, e.Error = null, and e.UserState = null. The message contained within e.Cancelled is simply "Operation has been cancelled". I imagine I'm receiving the TargetInvocationException
from an invalid cast of e.Result (since it's null). What I want to know though, is how come e.Error is still null and e.Canceled doesn't contain any helpful information about why the operation was canceled?
3.
When I tried setting e.Canceled = true;
from within DoWork on an exception catch, I managed to trigger the else if (e.Cancelled) {
line in my RunWorkerCompleted
function. I thought this was reserved for the user requesting the job being canceled though? Am I fundamentally misunderstanding how the background worker functions?
I tried this little test program and it works as expected:
static void Main(string[] args)
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, e) => { throw new ArgumentException(); };
worker.RunWorkerCompleted += (sender, e) => Console.WriteLine(e.Error.Message);
worker.RunWorkerAsync();
Console.ReadKey();
}
But when i run this programm within the debugger i also got the message about an unhandled exception at the throw-statement. But i simply pressed F5 again and it continued without any problems.
You're on the right track. In the RunWorkerCompleted
event, the e.Error argument contains any exception that was thrown. In this case, you should treat your
if (e.Error != null) {...}
as the catch block of a try
that you ran your background worker in, if that makes sense.
RunWorkerCompleted is fired every time when operation in DoWork is finished, cancelled or throwing an exception. Then in RunWorkerCompleted you check RunWorkerCompletedEventArgs if Error is not null. Error property is automatically set when exception in DoWork occurs. No need for try-catch in this particular case.
You are on right track.
Set e.Cancel = true
in the catch block on the doEvent if any error occur. Set WorkerSupportsCancellation Property true first.
In the DoWork event.
private void bw_DoWork( object sender, DoWorkEventArgs e )
{
try
{
if( !backgroundWorkder.CancellationPending )
{
// ....
}
}
catch
{
if (bgWorker.WorkerSupportsCancellation && !bWorker.CancellationPending)
{
e.Cancel = true;
e.Result = "Give your error";
return;
}
}
}
In OnRunWorkerCompleted Method.
private void BW_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
if( e.Cancelled )
{
MessageBox.Show( e.Result.ToString());
return;
}
}
If you do not do any exception handling in the DoEvent. BackgroundWorker itself do this for you.
If an exception is raised during an asynchronous operation, the class will assign the exception to the Error property. The client application's event-handler delegate should check the Error property before accessing any properties in a class derived from AsyncCompletedEventArgs; otherwise, the property will raise a TargetInvocationException with its InnerException property holding a reference to Error.
The value of the Error property is null if the operation was canceled.
In that case.
private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled the operation.
}
}
See here for more details.
精彩评论