I write code, which call thread with parameters. But my program is windows forms. So how change code, that wait thread, and that GUI of my program not freeze?
var t=new Thread(()=>SomeMethod(SomeParameter));
t.Start();
//t.Wait?
If you don't have await available, the simplest solution is to use a BackgroundWorker:
var bw = new BackgroundWorker();
bw.DoWork += (sender, args) => {
// do your lengthy stuff here -- this will happen in a separate thread
...
}
bw.RunWorkerCompleted += (sender, args) => {
if (args.Error != null) // if an exception occurred during DoWork,
MessageBox.Show(args.Error.ToString()); // do your error handling here
// Put here the stuff you wanted to put after `t.Wait`
...
}
bw.RunWorkerAsync(); // start the background worker
RunWorkerCompleted runs in the UI thread, so you will be able to update your UI there (as opposed to stuff happening in DoWork).
Related
I am trying to add a child control (UserControl) to a Grid and the changes are not reflecting. But if another child control (UserControl) is added to the same grid then layout gets updated and both child are visible. This operation perform on button click.
/*this Operation is perform in Backgroud Worker */
void func()
{
/*adding first User Control*/
addRemoveChild(true,FirstChild);//even tried to run this fuc with Dispatcher
FixButton();
addRemoveChild(false,FirstChild);
}
void addRemoveChild(bool isAdd,UserControl uc)
{
if (isAdd)
{
parentGrid.Children.Add(uc); /*parentGrid is Parent Grid*/
parentGrid.UpdateLayout();
return;
}
else
{
parentGrid.Children.Remove(uc);
parentGrid.UpdateLayout();
}
}
void FixButton()
{
/* here some operation is perform which takes 5 min to complete till then FirstChild is not visible*/
addRemoveChild(true,secondChild); /*When this Func run the first Child is visible*/
}
Your function is performed in a background worker: It's not done in the Dispatcher Thread. Every time you use a Dispatcher object (an object that has been created by the Dispatcher thread such as Controls) you should be in the Dispatcher thread.
The background worker is useful to perform task and update UI relative to the task's status in "real time".
You aren't using the background worker correctly. The code in the DoWork is executed in a separate thread whereas the ProgressChanged callback is executed in the Dispatcher thread.
What your code should look like this:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (sender, args) => {
bw.ReportProgress(0);
FixButton();
bw.ReportProgress(100);
};
bw.ProgressChanged += (sender, args) => {
if (args.ProgressPercentage == 0) {
parentGrid.Children.Add(uc);
} else if(args.ProgressPercentage == 100) {
parentGrid.Children.Remove(uc);
}
};
bw.RunWorkerAsync();
By the way you don't need to call UpdateLayout() and your DoWork callback function should never use Dispatcher objects (remove addRemoveChild from FixButton function)
I've made an app that runs around the clock with three Backgroundworkers running in different intervals.
In their DoWork i do some Dispatcher.BeginInvoke so it updateds some charts. The problem is that its crashing during the night and I'm unsure why. I've wrapped the Dispatcher.BeginInvoke in try/catch, but since I'm invoking the UI thread, I'm thinking I maybe do the try catch INSIDE the Dispatcher.BeginInvoke instead.
Does it matter?
test if the EventArgs Error property is not null in the RunWorkerCompleted method
backgroundWorker.RunWorkerCompleted += (s, e) =>
{
if (e.Error != null)
{
// handle error
}
}
The try/catch will only work with the stuff that's happening in its current thread. If an error happened in another thread, the worker will finish but you don't know why unless you inspect the Error property.
A callback queued with Dispatcher.BeginInvoke is asynchronous. You should observe all exceptions inside the delegate you pass into Dispatcher.BeginInvoke, because they are not getting propagated anywhere outside it (except as Application.Current.DispatcherUnhandledException, AppDomain.CurrentDomain.UnhandledException events and as DispatcherOperation.Task.Exception property, see below). If they go unhanded, they will crash the app inside the core Dispatcher event loop on the UI thread.
This includes RunWorkerCompletedEventArgs.Error too. An exception thrown inside Dispatcher.BeginInvoke delegate will not be available there as Error, upon RunWorkerCompletedEvent event.
Here is a simple example illustrating the problem. Note how e.Error is null inside RunWorkerCompleted:
// UI Thread
// prepare the message window
var window = new Window
{
Content = new TextBlock { Text = "Wait while I'm doing the work..." },
Width = 200,
Height = 100
};
// run the worker
var dispatcher = Dispatcher.CurrentDispatcher;
var worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
// do the work
Thread.Sleep(1000);
// update the UI
dispatcher.BeginInvoke(new Action(() =>
{
throw new ApplicationException("Catch me if you can!");
}));
// do more work
Thread.Sleep(1000);
};
worker.RunWorkerCompleted += (s, e) =>
{
// e.Error will be null
if (e.Error != null)
MessageBox.Show("Error: " + e.Error.Message);
// close the message window
window.Close();
};
// start the worker
worker.RunWorkerAsync();
// show the modal message window
// while the worker is working
window.ShowDialog();
To solve the problem, observe the exceptions with something like this:
var step = 0; // progress
// do the work on a background thread
// ..
var lastStep = step++;
Dispatcher.BeginInvoke(new Action(() =>
{
try
{
// do the UI update
}
catch(Exception ex)
{
// log or report the error here
MessageBox.Show("Error during step #" +
lastStep + ": " + ex.ToString());
}
}));
Alternatively, you can keep track of all DispatcherOperation returned by Dispatcher.BeginInvoke:
var invokes = new List<DispatcherOperation>();
// do the work on a background thread
// ..
invokes.Add(Dispatcher.BeginInvoke(new Action(() =>
{ /* update the UI */ }))));
Then you can examine DispatcherOperation.Task.Exception of each invocation you've queued with Dispatcher.BeginInvoke. I don't think this is feasible though, unless you can prevent the invokes list from growing endlessly.
I want to show in realtime (updating every 1s for example) some temperatures to my program's interface.
To do this I believe I'm going to need to run some code in a background worker so the main program doesn't get blocked. My question here is if it's possible to set the text of a TextBlock from a background worker and if yes, how to do it.
This is the basic idea:
backgroundworker
{
while(true)
{
//reading and updating temperatures
//.....
}
}
BackgroundWorker has built in support for reporting the current progress of the work, which sounds like it's exactly what you're doing:
var worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += (s, args) =>
{
while (true)
{
Thread.Sleep(1000);//placehodler for real work
worker.ReportProgress(0, "Still working");
}
};
worker.ProgressChanged += (s, args) =>
{
textBox1.Text = args.UserState as string;
};
worker.RunWorkerAsync();
By leveraging the built in support you allow the background worker to handle marshaling to the UI thread. (It will ensure that all of the events besides DoWork run in the UI thread.)
This also has the advantage of separating the UI logic from the business logic, rather than embedding code for manipulating the UI all throughout code doing business work.
I have to load a window and in Window_Loaded I have to load some variables and show it on Window.
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
try
{
//code to download some variables which will show on UI of Window Loading
}
catch (Exception ex)
{
//The calling thread cannot access this object because a different thread owns it.
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
};
worker.RunWorkerAsync();
}
But I am getting a threading exception. Is there any way to show the variables value on window from DoWork of Backgroundworker?
You should retrieve the data you need in the DoWork section, then assign it to ea.Result, which will make it available in the RunWorkerCompleted section.
In the RunWorkerCompleted section, you can access ea.Result again, casting the object back to whatever type you assigned in DoWork, and apply the data as needed to your UI controls.
worker.DoWork += (o, ea) =>
{
ea.Result = GetMyData();
};
worker.RunWorkerCompleted += (o, ea) =>
{
var myData = (myDataType)ea.Result;
// Assign myData as needed to UI components...
};
You need to let Dispatcher schedule your code to execute on UI thread and marshal necessary parameters. Try something like this:
Dispatcher.Invoke(
new Action<string>(() =>
{
// Access UI from here
}),
DispatcherPriority.Normal
);
Although this (or something like this, since this is notepad code) will solve your problem, you should consider using MVVM pattern in your implementation. Then you will be able to make changes to ViewModel (just update data) and UI will update accordingly.
The whole point of the backgroundWorker is to update the UI after a time-consuming task. The component works as advertised in my WPF app.
However in my test, the callback is not invoked on the calling thread.
[Test]
public void TestCallbackIsInvokedOnClientThread()
{
var clientId = Thread.CurrentThread.ManagedThreadId;
int callbackThreadId = -1;
var manualEvent = new ManualResetEventSlim(false);
var someUIControl = new TextBox();
var bw = new BackgroundWorker();
bw.DoWork += (s,e) => e.Result = 5 ; // worker thread
bw.RunWorkerCompleted += (s, e) =>
{
try
{
callbackThreadId = Thread.CurrentThread.ManagedThreadId;
//someUIControl.Text = callbackThreadId.ToString();
manualEvent.Set();
}
catch (System.Exception ex)
{
Console.Out.WriteLine(ex.ToString());
}
};
bw.RunWorkerAsync();
if (!manualEvent.Wait(5000))
Assert.Fail("no callback");
Assert.AreEqual(clientId, callbackThreadId);
}
Result Message: Assert.AreEqual failed. Expected:<15>. Actual:<10>. callback not invoked on client Thread
What am I missing ?
In the Unit Test I see behavior like
------ Run test started ------
MainThread Id =21
Worker Thread Id =9
Callback Thread Id =9
In the Wpf App, this would be
MainThread Id =1
Worker Thread Id =14
Callback Thread Id =1
Update:
With Justin's answer, made the following changes and now the test passes
Before creating the BackgroundWorker
SynchronizationContext.SetSynchronizationContext(new DispatcherSynchronizationContext(control.Dispatcher));
Instead of using a event for signalling between the threads, simulate a message pump
.
for (int i = 0; i < 3; i++)
{
control.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
Thread.Sleep(50);
}
The behavior is different dues to the different contexts that you are running under.
When you call bw.RunWorkerAsync(), the SynchronizationContext is captured. This is used to dispatch out the RunWorkerCompleted call.
Under WPF it will use DispatcherSynchronizationContext which will marshall the completed call back to the UI thread. Under the test, this marshalling is unnecessary so it remains on the background worker thread.
I belive that the calling thread must support messagepumping (mean, being STA apartment and having an associated Dispatcher) so the background worker can post the callback. If it does not, the background worker has no option but execute the callback in its own thread. If you want to test it, see this link.
I ran into a problem in my code where the user closing a window caused a save, that in turn used a BackgroundWorker to update the home window and it did not run the RunWorkerCompleted because the thread that started the BackgroundWorker had terminated when the window closed.
I had to change the closing window's save run in the home window's context so that after the BackgroundWorker completed, it had a thread to return to.
In my case I am using Windows Forms and controls don't have a Dispatcher property (see the answer in no definition for dispatcher).
Gishu's solution works as well if we use Dispatcher.CurrentDispatcher instead of the one in the control.
On test initialisation:
// I am using a field Dispatcher _dispatcher
_dispatcher = Dispatcher.CurrentDispatcher;
And then when waiting for the background task to be completed:
_dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
Thread.Sleep(50);