I am trying to update an ObservableCollection that is data bound to the UI. I know that to do this I need to use Dispatcher and BeginvInvoke(), and to make it so that the UI doesn't freeze up when I do so, using a BackgroundWorker is a good way to go about it. In any event, I have all this, compiled and running, but nothing happens. I need to update the UI every 2 minutes or so, so I am also using a DispatcherTimer
This works, because DispatcherTimer is part of Dispatcher, but freezes the UI:
DispatcherTimer dispTimer = new DispatcherTimer();
dispTimer.Tick += dispTimer_Tick;
dispTimer.Interval = new TimeSpan(0, 0, 45);
dispTimer.Start();
private void dispTimer_Tick(object sender, EventArgs e)
{
PartialEmployees.Clear();
}
So, using the BackgroundWorker I pieced together this:
DispatcherTimer dispTimer = new DispatcherTimer();
dispTimer.Tick += dispTimer_Tick;
dispTimer.Interval = new TimeSpan(0, 0, 45);
dispTimer.Start();
private void dispTimer_Tick(object sender, EventArgs e)
{
BackgroundWorker _worker = new BackgroundWorker();
_worker.DoWork += DoWork;
_worker.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
Dispatcher.CurrentDispatcher.BeginInvoke( new Action(()=>
{
PartialEmployees.Clear();
}));
}
But nothing happens to the UI. What am I missing/not doing correctly?
You have two problems:
When you use Dispatcher.CurrentDispatcher from the background thread, it is getting the background thread's Dispatcher, not the UI thread's Dispatcher.
From your description I gather that your PartialEmployees.Clear() method takes significant time to execute and you want to avoid locking the UI thread during the execution. However, having a BackgroundWorker invoke PartialEmployees.Clear() on your UI thread will have the same effect as using the DispatcherTimer, so you need a different solution than the one you are going for.
If you only want to fix the Dispatcher.CurrentDispatcher problem, just store the current Dispatcher in a local variable like this:
private void dispTimer_Tick(object sender, EventArgs e)
{
var uiDispatcher = Dispatcher.CurrentDispatcher;
BackgroundWorker _worker = new BackgroundWorker();
_worker.DoWork += (sender, e) =>
uiDispatcher.BeginInvoke(new Action(() =>
{
PartialEmployees.Clear();
}));
_worker.RunWorkerAsync();
}
This will cause your UI change to work but it will still lock up the UI during the change, exactly as if you had not used BackgroundWorker. The reason for this is:
The DispatcherTimer fires, executing on the UI thread. All it does (dispTimer_Tick) is start a BackgroundWorker and then exit.
The BackgroundWorker executes on its own therad. All it does is schedule a Dispatcher callback and then exit.
The Dispatcher callback executes on the UI thread again. It calls PartialEmployees.Clear() which takes a while, locking up your UI thread while it executes.
So your behavior is the same as if the DispatcherTimer callback had called PartialEmployees.Clear() directly: In each case the time-consuming operation is executed on the UI thread.
The reason for the lockup is that any time you do a large piece of work on the UI thread you will get a momentary lockup while it runs. The solution is to break your work into smaller portions and do them one at a time, either from a DispatcherTimer or a BackgroundWorker. In your case, examine the code for PartialEmployees.Clear() to see if it can be done incrementally.
The problem here is that you're using the method Dispatcher.CurrentDispatcher from the back ground thread. What you need is the Dispatcher instance for the UI thread.
_worker.DoWork += delegate { DoWork(Dispatcher.CurrentDispatcher); };
...
private void DoWork(Dispatcher dispatcher) {
dispatcher.BeginInvoke(new Action(() => {
PartialEmployees.Clear();
});
}
I dont think you need the background work, as BeginInvoke on the Dispatcher runs on a Threadpool thread.
something like this should work, and is more succinct
DispatcherTimer dispTimer = new DispatcherTimer
{Interval = TimeSpan.FromSeconds(45)};
dispTimer.Tick += (o,e) => Dispatcher.CurrentDispatcher
.BeginInvoke((Action)PartialEmployees.Clear);
dispTimer.Start();
Related
My Backgroundworker loads a new "pop-up" form, but how do I terminate background worker and the newly created form?
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BussyForm bussyForm = new BussyForm();
bussyForm.ShowDialog();
}
This has no effect:
backgroundWorker1.Dispose();
backgroundWorker1.CancelAsync();
backgroundWorker1 = null;
You shouldn't be showing a form from a non-UI thread. You should only have one UI thread, and it, and only it, should access all of your user interface controls. Your non-UI threads shouldn't access UI elements at all.
You should be showing the given busy popup from the UI thread instead.
Requesting cancellation from the BackgroundWorker, or disposing of it, won't close the form, or force the thread to stop executing, which is why your form stays open.
Instead just show your popup from the UI thread when you start the background worker, and have the BGW's completed event call Close on the form:
private void button1_Click(object sender, EventArgs args)
{
BusyForm busyForm = new BusyForm();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += (_, e) => { busyForm.Close(); };
worker.RunWorkerAsync();
busyForm.ShowDialog();
}
CancelAsync doesn't actually abort your thread or anything like that. It sends a message to the worker thread that work should be cancelled via BackgroundWorker.CancellationPending. Your DoWork delegate that is being ran in the background must periodically check this property and handle the cancellation itself.
You call bussyForm.ShowDialog(); so you need to close this form manually.
I have a multithreaded program. I want start the new BackgroundWorker and pause the current thread. Then I want resume previous thread in the new BackgroundWorker.
I am programming in C#.
I have a big project and cannot put my code here.
You can use AutoResetEvent and use WaitOne to hold the parent thread. Call AutoResetEvent.Set method from spawned thread to resume the execution of parent (main) thread.
childThread.Start();
autoResetEvent.WaitOne();
In child (spawned thread)
private void SpawnedThread()
{
//your code
autoResetEvent.Set(); //will resume the execution after WaitOne(), may be under some condition.
}
You can use overloaded version of WaitOne to give the maximum wait time. The execution will resume of Set method is not being called until the give time.
Here is my sample code for you! I'm not so sure it useful for your project, but this is my idea. hope helpful.
BackgroundWorker bwExportLogFile = new BackgroundWorker();
private void ExportLogFile() {
bwExportLogFile.DoWork += bwExportLogFile_DoWork;
bwExportLogFile.RunWorkerCompleted += bwExportLogFile_RunWorkerCompleted;
bwExportLogFile.ProgressChanged += bwExportLogFile_ProgressChanged;
bwExportLogFile.RunWorkerAsync();
bwExportLogFile.WorkerReportsProgress = true;
bwExportLogFile.WorkerSupportsCancellation = true;
}
void bwExportLogFile_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if(some thing is true here){
bw.CancelAsync();
}
}
So when you want to run thread in BackgroundWorker again just call this:
bwExportLogFile.RunWorkerAsync();
Try to set WorkerSupportsCancellation = true and in ProgressChanged event you can do like this:
BackgroundWorker bw = sender as BackgroundWorker;
bw.CancelAsync();
bw.RunWorkerAsync();
I have the following code which updates my progress bar and status bar from a backgroundworker. I run the same backgroundworker twice. The first time I run it I call it from the MainWindow constructor it works fine. At the end of the constructor I setup a timer to call the method every so often.
System.Threading.TimerCallback timerCallback = new System.Threading.TimerCallback(RefreshWebDataTimer);
timer = new System.Threading.Timer(
timerCallback, null,
Dictionary.MS_TIMER_FIRSTREFRESH_PERIOD,
Dictionary.MS_TIMER_REFRESH_PERIOD);
When calling it from the timer I get the following error:
A first chance exception of type 'System.InvalidOperationException'
occurred in WindowsBase.dll Additional information: The calling thread
cannot access this object because a different thread owns it.
I added some debug and indeed the Dispatcher Thread is on a different thread from the timer and the same thread from the original run.
private void backgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
System.Diagnostics.Debug.Print("Current Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
System.Diagnostics.Debug.Print("Dispatcher Thread: {0}", progressBar.Dispatcher.Thread.ManagedThreadId);
this.progressBar.Visibility = Visibility.Visible;
this.progressBar.Value = e.ProgressPercentage;
if (e.UserState != null)
{
this.statusBar.Text = e.UserState.ToString();
}
}
Current Thread: 22
Dispatcher Thread: 7
I was under the impression that the ProgressChanged and RunWorkerCompleted events always ran on the main UI thread in order to solve this problem and be able to do UI updates. Apparently, I misunderstand what is going on here.
I updated my solution to use the Dispatcher as follows:
private void backgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
progressBar.Dispatcher.BeginInvoke(new OneArgIntDelegate(updateProgressBar), e.ProgressPercentage);
if (e.UserState != null)
{
progressBar.Dispatcher.BeginInvoke(new OneArgStrDelegate(updateStatusBar), e.UserState.ToString());
}
}
private void updateStatusBar(string Text)
{
this.statusBar.Text = Text;
}
private void updateProgressBar(int ProgressPercentage)
{
this.progressBar.Visibility = Visibility.Visible;
this.progressBar.Value = ProgressPercentage;
}
This solution worked but I thought the whole point of the BackgroundWorker was that I didn't have to do this. Can someone explain my incorrect assumption and what is really going on. Is there a way to do this WITHOUT the dispatcher by setting up the timer differently?
Thanks,
Harrison
I was under the impression that the ProgressChanged and
RunWorkerCompleted events always ran on the main UI thread in order to
solve this problem and be able to do UI updates. Apparently, I
misunderstand what is going on here.
The BackgroundWorkers ProgressChanged is called back to the thread that owns the BackroundWorker, this is not aways the UI thread, When you create the BackgroundWorker the second time it is being created on another thread so the ProgressChanged will be invoked on the thread that created the BackgroundWorker in this case the timer thread.
First Call: UIThread => BacgroundWorker => Progress => UIThread
SecondCall: TimerThread => BacgroundWorker => Progress => TimerThread
You can ether invoke the RefreshWebDataTimer from the Timer to the UI thread or use a DispatcherTimer to ensure the RefreshWebDataTimer is called on the UI thread.
Option 1:
timer = new System.Threading.Timer(
(o) => Dispatcher.Invoke((Action)RefreshWebDataTimer),
null,
Dictionary.MS_TIMER_FIRSTREFRESH_PERIOD,
Dictionary.MS_TIMER_REFRESH_PERIOD);
Option 2:
timer = new DispatcherTimer(
TimeSpan.FromSeconds(1),
DispatcherPriority.Background,
(s, e) => RefreshWebDataTimer(),
Dispatcher);
To play a bit with threading, delegates and backgroundworkers, I'm putting together a few small applications, I'm having a bit of trouble with one of them.
I've a Windows form, with a textbox, a button and a richttext.
When I press the button, the text in the textbox is used as a paramter to instantiate a class, like this:
public partial class Form1 : Form
{
private BackgroundWorker backgroundWorker;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
backgroundWorker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.AppendText(response);
Application.DoEvents();
}
});
}).Start();
}
void OnUpdateTicker(string msg)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
label4.Text = msg;
Application.DoEvents();
});
}).Start();
}
}
When debugging I run into a 'textBox1.Lines' threw an exception of type 'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException'
Any tips on how to solve this problem?
First, there is no need to create new threads inside DoWork; the whole idea with the BackgroundWorker is that DoWork is executed on a separate thread. Second, since DoWork is executed on a separate thread and UI controls can be modified only on the UI thread, you need to invoke those updates correctly. So, a rewritten version of worker_DoWork could look like this:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.Invoke((Action) delegate { richTextBox1.AppendText(response); });
}
}
Note how the code does not explicitly spawn any new threads, and also how the AppendText method call is done through a Control.Invoke call, forcing it to execute on the UI thread.
The main reason is that the textbox is not owned by the background thread.
Your UI thread owns all the UI objects, and you're spinning up a background thread when a button is pressed. That background thread should not have access to any UI objects.
If you want the value of the textbox to be used, you'll need to pass it to your background thread another way.
Have a look here for an explanation (and solution).
You can only update controls on the main thread from the main thread itself, unless you explicitly tell your program that it's ok to do, by using the .Invoke method of the control.
From: http://www.albahari.com/threading/part3.aspx
Control.Invoke
In a multi-threaded Windows Forms application, it's illegal to call a method or property on a control from any thread other than the one that created it. All cross-thread calls must be explicitly marshalled to the thread that created the control (usually the main thread), using the Control.Invoke or Control.BeginInvoke method. One cannot rely on automatic marshalling because it takes place too late – only when execution gets well into unmanaged code, by which time plenty of internal .NET code may already have run on the "wrong" thread – code which is not thread-safe.
good evening!
currently i'm developing a wpf-client for some rest-service. the communcation with the rest-service is no problem and is done in an extra assembly (communcation-interface).
basically:
i have a somehow "search"-button which executes a method. this method communicates with the service, updates some textboxes and a progress-bar (to give the user some graphic info, how far we are ...).
unfortunaly the server, which hosts the service is a bit lame, causing some severe response-time (about 4 secs). this, on the other hand, causes my wpf-application to wait, which ends up in: going black, and titeling "not responding" ...
i've already tried to put this execution in another thread, but ... it's logical that i won't get any access to the controls of my wpf-window ...
atm i'm really helpless ... can anyone give me some handeling-routine or a solution?
Your UI thread is busy waiting on a response from the web service, and isn't available to paint the screen. One good option, is push the service request off to another, non-UI thread. Look into BackgroundWorker, which was designed specifically to make this easy. It handles marshalling of cross-thread calls from non-UI to UI threads.
Roughly:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync(arg);
...
static void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
int arg = (int)e.Argument;
e.Result = CallWebService(arg, e);
}
static void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Increment();
}
static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label.Text = "Done: " + e.Result.ToString();
}
To access your controls from a second thread use Dispatcher.BeginInvoke:
Dispatcher.BeginInvoke(new Action(() =>
{
// Update your controls here.
}), null);
Or you can look into using BackgroundWorker.