Please explain this C# code snippet - c#

I see people write code like this
_Worker = new BackgroundWorker();
_Worker.DoWork += (sender, e) => e.Result = ((Func<string>)e.Argument)();
Why people assign e.Result if they don't use it. There is no code below that uses this assignment.
But! They write below a piece of code where use RunWorkerCompleted event. Does RunWorkerCompleted event use e automatically when rises (precisely this e above that I assigned)?

You are right. The e.result can be used in the RunWorkerCompleted Event. Because DoWork Runs in a own Thread that's the way how you can reuse your results to main thread. Here you can read more about it.
Notice that backgroundworker is a bit oldschool and read about Task Library if you are interested in modern C# Multithreading purposes ( >=.Net 3.5)
Small Example:
_Worker = new BackgroundWorker();
_Worker.DoWork += (sender, e) => e.Result = ((Func<string>)e.Argument)();
_Worker.RunWorkerCompleted += (sender,e) => textbox.Text = e.Result.ToString();

Related

Windows Form run external process without blocking UI

I want to:
Show a form with a textbox.
Run an external program (notepad.exe for ease of example).
Continue to allow the user to enter data into the form textbox whilst notepad is running.
Run some more (continue) native form code when notepad closes. This will update the form, amongst other things.
I'm having problems making this happen. I'm aware of a multitude of posts about this similar issue, but haven't found a solution that works for me.
I have tried:
Doing a waitforexit, but this of course blocks the UI and users cannot enter data.
Attempting an asynchronous process call, where another method is called when this process is completed. This causes a problem where the new method is called from another thread and can't update the form.
Doing a wait/sleep loop in the UI, but again this will naturally block the UI.
What would be the neatest, and simplest solution for a simple Windows Form program? There are no extra classes used, and all code is in the Form1 class.
The Process class fires an Exited event when the process exits. You can add a handler to that event to execute code when the process exits without blocking the UI thread:
process.EnableRaisingEvents = true;
process.Exited += (s, args) => DoStuff();
Alternatively you could create a Task that represents the completion of the process to leverage the TPL for asynchrony:
public static Task WhenExited(this Process process)
{
var tcs = new TaskCompletionSource<bool>();
process.EnableRaisingEvents = true;
process.Exited += (s, args) => tcs.TrySetResult(true);
return tcs.Task;
}
This would allow you to write:
await process.WhenExited();
UpdateUI();
Here you go:
void Form1_Load(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
var p = Process.Start("notepad.exe");
p.WaitForExit();
}).ContinueWith(antecedant => { MessageBox.Show("Notepad closed"); });
}
Here is my favorite way to do something like this with a BackgroundWorker. This has the advantage of the RunWorkerCompleted callback being on the main thread, so it can interact with the UI.
public partial class Form1 : Form
{
...
private BackgroundWorker wrk;
private void button1_Click(object sender, EventArgs e)
{
wrk = new BackgroundWorker();
wrk.DoWork += (s, ea) => { /*Create your process and wait here*/ };
wrk.RunWorkerCompleted += (s, ea) => { textBox1.Text = "Finished"; };
wrk.RunWorkerAsync();
}
}
You should start process in BackgroundWorker so you can catch complete event on same thread.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate {
Process proc = Process.Start("YOUR-PROCESS-PATH");
proc.Start();
proc.WaitForExit();
}
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.RunWorkerAsync();
then catch the worker ended event on called thread;
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Do your thing o UI thread
}

Windows Forms: UI threads flow with Show() and ShowDialog()

While developing a solution on Windows Forms I went into a routine of showing continuous progress to user. I implemented simple dummy window with continuous progress bar:
In solution tree it is situated on the same level as the Main Window:
The simplest working approach to show continuous progress while doing something is the following code. It does work:
//This method works
private void DoSomeBackgroundStuffWithShow()
{
ContinuousProgressWindow continuousProgressWindow =
new ContinuousProgressWindow();
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender, arguments) =>
{
//Do some stuff for 4 seconds
Thread.Sleep(4000);
};
backgroundWorker.RunWorkerCompleted += (sender, arguments) =>
{
//Window is closed when needed. Great!
continuousProgressWindow.Dispose();
};
continuousProgressWindow.Show(this);
backgroundWorker.RunWorkerAsync();
}
But I need this window to appear topmost and block its parent while working. The following code is quite similar, and it does not work - the dialog is shown, but never closed:
//This method DOES NOT WORK
private void DoSomeBackgroundStuffWithShowDialog()
{
ContinuousProgressWindow continuousProgressWindow =
new ContinuousProgressWindow();
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender, arguments) =>
{
//Do some important stuff for 4 seconds
Thread.Sleep(4000);
};
backgroundWorker.RunWorkerCompleted += (sender, arguments) =>
{
//None of the following work for "ShowDialog() method"
//Ran with debugger - breakpoints not hit!
continuousProgressWindow.DialogResult = DialogResult.OK;
continuousProgressWindow.Close();
continuousProgressWindow.Dispose();
};
continuousProgressWindow.ShowDialog(this);
backgroundWorker.RunWorkerAsync();
}
Then, I realize the problem is about UI threads flow: when the progress window is ran as a dialog, MainWindow thread is frozen and it cannot be invoked by BackgroundWorker in RunWorkerCompleted delegate to close the dialog.
What is the simplest solution to make it work as wanted?
continuousProgressWindow.ShowDialog(this);
backgroundWorker.RunWorkerAsync();
You've got a simple chicken-and-egg problem, you don't start the worker until after the dialog closes. ShowDialog() is a blocking call. So the RunWorkerCompleted event doesn't fire because the worker didn't get started. The simplest workaround is to swap the two statements:
backgroundWorker.RunWorkerAsync();
continuousProgressWindow.ShowDialog(this);
That is not entirely safe to do. Not a problem with this snippet but in real code there is a danger that the worker completes before the dialog is displayed. Low odds but not zero. To solve that, you want to delay the worker until you are sure the dialog is up and running. That can be done with an AutoResetEvent that is Set() by the dialog's OnShown() method. Or, more elegantly, by taking advantage of a trick:
this.BeginInvoke(new Action(() => backgroundWorker.RunWorkerAsync()));
continuousProgressWindow.ShowDialog(this);
The delegate target of Control.BeginInvoke() runs when the program re-enters the message loop. That happens after the dialog becomes visible :)
The issue here is that you are calling continuousProgressWindow.ShowDialog(this) before backgroundWorker.RunWorkerAsync(). So backgroundWorker.RunWorkerAsync() will be called once you close the window.
I think following code should work, as suggested by #Steven Mills also.
private void DoSomeBackgroundStuffWithShowDialog()
{
ContinuousProgressWindow continuousProgressWindow =
new ContinuousProgressWindow();
BackgroundWorker backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (sender, arguments) =>
{
//Do some important stuff for 4 seconds
Thread.Sleep(4000);
};
backgroundWorker.RunWorkerCompleted += (sender, arguments) =>
{
//None of the following work for "ShowDialog() method"
//Ran with debugger - breakpoints not hit!
continuousProgressWindow.DialogResult = DialogResult.OK;
continuousProgressWindow.Close();
continuousProgressWindow.Dispose();
};
backgroundWorker.RunWorkerAsync();
continuousProgressWindow.ShowDialog(this);
}

Canceling Backgroundworker that was set using a lambda

I have a backgroundworker that has been created using a lambda as shown here:
BackgroundWorker fileCountWorker= new BackgroundWorker();
fileCountWorker.WorkerSupportsCancellation = true;
fileCountWorker.DoWork += new DoWorkEventHandler((obj, e) => GetFileInfo(folder, subs));
fileCountWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountInFolderListViewForItem(index));
fileCountWorker.RunWorkerAsync();
I would like to be able to cancel the backgroundworker, and then know that it was canceled in the RunWorkerCompleted function using the RunWorkerCompletedEventArgs e.Canceled property.
So far I have been unable to figure out a way to pass a parameter to the RunWorkerCompleted function and still maintain the ability to access the RunWorkerCompletedEventArgs.
I tried adding a RunWorkerCompletedEventArgs parameter to the function called by RunWorkerCompleted, and then passing the RunWorkerCompletedEventArgs like so:
fileCountWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountInFolderListViewForItem(index, e));
But that didn't seem to work.
Is there a way to do this?
Edit:
Following comments below, I made the following changes:
I changed the DoWork Event as follows (adding the obj and e as parameters in the worker function):
fileCountWorker.DoWork += new DoWorkEventHandler((obj, e) => GetFileInfo(folder, subs,obj,e));
I then changed the RunWorkerCompleted function as follows (adding the obj and e as parameters in the RunWorkerCompleted function):
fileCountWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountInFolderListViewForItem(index, obj, e));
From my UI Thread I call CancelAsync:
if (bgw.WorkerSupportsCancellation)
{
bgw.CancelAsync();
}
Then from within the backgroundworker I check for cancellationpending like:
BackgroundWorker bwAsync = sender as BackgroundWorker;
if (bwAsync.CancellationPending)
{
e.Cancel = true;
return;
}
The result is that when I cancel the backgroundworker, it does stop the worker function, but theRunWorkerCompletedEventArgs in the RunWorkerCompleted function ( UpdateCountInFolderListViewForItem) still has a Canceled property set to False, so the function can't tell that the worker was canceled.
So i'm still stuck on getting the RunWorkerCompleted function to know that the worker was canceled instead of completing normally.
You just need to call BackgroundWorker.CancelAsync().
Your worker code needs to check BackgroundWorker.CancellationPending and stop what it's doing to "cancel"... But, your lambda isn't doing anything you can really cancel.
Normally what you'd do is something like this:
//...
fileCountWorker.DoWork += (obj, e) =>
{
for (int i = 0; i < 1000 && fileCountWorker.CancellationPending; ++i)
{
Thread.Sleep(500);/* really do other work here */
}
e.Cancel = fileCountWorker.CancellationPending;
};
fileCountWorker.RunWorkerAsync();
//...
fileCountWorker.CancelAsync();
If you provide some details of GetFileInfo, maybe some more detail could be provided.

How do I run task like `object = ClassA.GetObject()` in a separate thread using BackgroundWorker?

I'm trying to make this line to work with BackgroundWorker:
map = Map.LoadMap(mapname);
…like this:
bw.DoWork += (map = Map.LoadMap(mapname));
It causes the error Cannot implicitly convert type 'game.Map' to 'System.ComponentModel.DoWorkEventHandler'.
I just started using BackgroundWorker as threading component for my game, but it doesn't look like it will be easy to convert all existing methods to work with it. Is there a simple way to make this work or is it better to switch to some other threading mechanism?
Note: from the threading base I need to be able to poll for progress percentage and not messing up my existing method calls.
You can leverage anonymous delegates like this:
bw.DoWork += (sender, args) => { map = Map.LoadMap(mapname); };
As I understand the type of variable map and the return type of method Map.LoadMap - are game.Map.
In your code in line
bw.DoWork += (map = Map.LoadMap(mapname));
you are doing next: get the result from Map.LoadMap(mapname), set it to variable map and after that try to use this value as a handler for DoWork event. And the type of variable map and property bw.DoWork are different.
So you just need to change this line to:
bw.DoWork += (sender, eventArgs) => { map = Map.LoadMap(mapname); }
Which will mean that you are trying to create new Delegate "(sender, eventArgs) => ..." and use it as a handler for property bw.DoWork.
Backgroundworker is good because you can use the option WorkerReportsProgress = true
this can be used to pool for a percentage
you can report progress inside the DoWork method like this
bw.ReportProgress(percentage);
I use to associate BackgroundWorker as a wrapper for what Threads would do. So I use BackgroundWorker on GUI works, and Threads on more specialized or dirty jobs (Windows Services, etc)
you dowork method has to be written like this
bw.DoWork += (sender, args) => { map = Map.LoadMap(mapname); };
You can use the BackgroundWorker like this:
var worker = new System.ComponentModel.BackgroundWorker();
worker.DoWork += delegate
{
map = Map.LoadMap(mapname);
};
worker.RunWorkerAsync();
Keep in mind that the program will continue execution immediatly after the RunWorkerAsync() method so if you use the map variable afterwards it will probably not be a loaded map.
To continue execution after the map has been loaded you need to subscribe to the RunWorkerCompleted also:
var worker = new System.ComponentModel.BackgroundWorker();
worker.DoWork += delegate
{
map = Map.LoadMap(mapname);
};
worker.RunWorkerCompleted += delegate
{
MapComplete(); // contiune with stuff here
};
worker.RunWorkerAsync();
The += operator indicates that you are attaching an event handler (DoWork is an event).
Here is an example usage:
Create an instance of the backgroundworker(in this case it will be at the class level), call the function that attaches the events SetupBackgroundWorker()
private BackgroundWorker bw = new BackgroundWorker();
private void SetupBackgroundWorker()
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.ReportProgress = true;
}
These are sample event handlers, should give you an idea
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{//Just as an example, I don't ever call the functions to trigger this event
int ProgressPercent = e.ProgressPercentage;
object AnyOtherDataReported = e.UserState;
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Do something when the work has been completed
//Note: You should always check e.Cancelled and e.Error before attempting to touch the e.Result. I did not put that protection in this example.
object TheResultFrom_DoWork = e.Result;//This is your "map" object
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
//object PassedInObject=e.Argument; //This is the argument you sent to RunWorkerAsync
//Type cast PassedInObject to your correct Type
WhateverTypeItIs_YouDidntSay mapname=(WhateverTypeItIs_YouDidntSay)e.Argument
//Perform your task
object returnvalue=Map.LoadMap(mapname);//This was your varriable called "map"
//Assign the result of your task to the return value
e.Result=returnvalue;
}
Pass this function the value for mapname and if the backgroundworker is not busy doing a previous task, it should start the process.
private void ProcessTheMap_InBackground(WhateverTypeItIs_YouDidntSay mapname)
{
if (!bw.IsBusy)
{
bw.RunWorkerAsync(mapname);
}
else
{//You are already loading something in the background
}
}

Call BackgroundWorker RunWorkerCompleted event when timer stops

I have a BackgroundWorker that creates a timer. The timer makes repeated calls to a DataTable. I only want the BackgroundWorker.RunWorkerCompleted event to get called when the timer stops. How do I do this?
Thanks.
Just create a loop in the BackgroundWorker's DoWork event handler and repeat the loop until the timer stops. More or less like so:
var worker = new BackgroundWorker();
worker.DoWork += (sender, e) =>
{
var timer = new Timer();
timer.Elapsed += (s, _e) =>
{
// call the database
};
timer.Start();
while (timer.Enabled)
{
// at some point: timer.Stop();
}
// if we are here, timer is no longer Enabled
// RunWorkerCompleted event will be fired next
};
(Obviously I ommitted setting the timer's Interval etc.)
BackGroundWorker.RunWorkerCompleted is called after your DoWork event completed. So if you ensure the DoWork event completes your RunWorkerCompleted event should be called by the background worker.
Another solution is to create the timer outside your background worker and control the background worker instance from the timer. In the timer event, check if the IsBusy property is set to false and start the background worker or skip if IsBusy is true.
Sample:
_worker.DoWork += (sender, e) =>
{
BackgroundWorker worker = sender as BackgroundWorker;
// Do your database stuff
e.Result = databaseResult;
}
_timer.Elapsed += (source, e) =>
{
if(!_worker.IsBusy)
{
_worker.RunWorkerAsync();
}
}

Categories