How to handle long running "thread" in WPF? - c#

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.

Related

Progress Bar with Label — Unable to Update Label in ProgressChanged

I have a background worker with a long running task. The task goes through a list of files and I want to update the user with which file we are on. I have a tool strip that has a label named panel1.text. The progress bar is working however the label is not changing in my ProgressChanged method i.e. It should say Processing File1 then change to Processing File2, but it stays on the default of Processing.
private void btnProcess_Click(object sender, EventArgs e)
{
toolStripProgressBar1.Visible = true;
toolStripProgressBar1.Maximum = 1000000000;
panel1.Text = "Processing "; // this appears properly
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(processFiles);
worker.ProgressChanged += ProgressChanged;
worker.RunWorkerAsync();
while (worker.IsBusy)
{
// the reason for this is because nothing can happen until the processing is done
toolStripProgressBar1.Increment(1);
}
// more processing
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
panel1.Text = "Processing "+ e.UserState.ToString(); <<<---- This is Not Updating panel1.Text but it evaluates properly
}
private void processFiles(object sender, EventArgs e)
{
int retVal = 0;
foreach (string fileName in listBox1.Items)
{
ProgressChangedEventArgs ea = new ProgressChangedEventArgs(1,fileName);
ProgressChanged(this, ea);
// do more processing
}
}
I would appreciate any help.
You are using the same thread, which is being blocked by another process. You need to use a Task to create a new thread and possibly use Dispatcher.BeginIvoke if the control is on the other thread. Make sure whatever Button Click, etc is happening is marked with the Async keyword as well to make it Asynchronous.
Example:
Await Task mytask = Task.Run(() =>
for(var i = 0; i < 1000; i++)
{
Label.Dispatcher.BeginInvoke( () =>
UpdateMe(int i, LabelClass/Component class/component)});
Then inside the Label Class or wherever the label is:
Public void UpdateMe(int i, LabelClass class)
{
class.label.content = Cint((i/Total)*100);
Thread.Sleep(500);
}
There are other ways to do it as well such as Binding the value to the UI, but this will give you a better understanding of why its not working and how things work with other threads.
If you want to really get a visual understanding call:
`Console.WriteLine($"Current Thread ID: System.Threading.Thread.CurrentThread.ManagedThreadId}");`
Right before you go into the Task---it will give you the main thread ID
Then inside the Task call it again...this will give you the secondary thread ID.
Then Right before the Dispatcher call:
Console.WriteLine($"Do I have access to the label on this thread? {Label.Dispatcher.CheckAccess()}";
If you have access it will display True, if not it will display False...In your case it will display false because its owned by the other thread, but you can use the Dispatcher to be able to do work on that thread while in another thread...
Also, I recommend you not use Background Worker and use Tasks instead...this explains why in depth...basically Tasks do everything Background workers do and more, have less issues and are easier to work with...
http://blog.stephencleary.com/2013/09/taskrun-vs-backgroundworker-conclusion.html
As already commented by Ivan, remove the while loop while (worker.IsBusy) as it's blocking the UI thread to process further. As well, you should enable the WorkerReportsProgress to true
worker.WorkerReportsProgress = true;
worker.ProgressChanged += ProgressChanged;
while (!worker.IsBusy)
{
worker.RunWorkerAsync();
}
Per your comment, move those later processing to BackgroundWorker.RunWorkerCompleted Event

Abort a thread which is running a long query

I have a thread which calls one of the methods, now this method executes a query which can take a very long time possibly 40 minutes or so to complete,
I want to give user a a choice to be able to cancel this operation (meaning stop the thread and stop the query to release database).
I should mention that I am developing WPF Application using .net 4.5, SQL SERVER DB and C#.
You should use backgroundworker, it is exactly what you want.
Eather drag and drop it from the toolbox or create it in code - behind. It supports Cancellation, reports progress, notifies when complete and know if it is running or not.
Here is an example.
void method(){
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.ProgressChanged += worker_ProgressChanged;
worker.DoWork += worker_DoWork;
worker.WorkerSupportsCancellation = true;
if(!worker.IsBusy)
{
worker.RunWorkerAsync();
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//do whatever needs to be done on the other thread here.
object argument = e.Argument; //if passed argument in RunWorkerAsync().
object result = new object();
e.Result = result;
//after making worker global, you can report progress like so:
worker.ReportProgress(50); //you can also pass a userState, which can be any object, to show some data already.
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//you can update a progress bar in here
int progress = e.ProgressPercentage;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//when done
}
void CancelTheTask()
{
if (worker.IsBusy)
{
//make worker global first, but then
worker.CancelAsync();
}
}
A important things to look at: Never use resources in the DoWork method that are not created inside it. Thus pass things you need in the background worker as Arguments. And things that are created by the backgroundworker should not be set to a global variable ether, pass by result.
When cancelling, RunWorkCompleted will also be fired. Now the query to the database is already being executed, so that is still running, even when your application lost all resources to it.
To cancel that, we would need to know how you execute the query, like #S.Akbari mentioned is one way. Entity Framework 6 also supports cancellation.
For that: check this when using Queryable
here is another example
Or this solution without Entity Framework.
Using Task Parallel Library (TPL) you can use the Task Cancellation pattern.
When you have your Thread blocked on waiting for the query, it's useless for stopping anything.
Make sure the SqlConnection of the query is accessible from your UI and Close it. Abandon the Thread, it will terminate (with an error you've got to suppress).
If the UI thread is doing a Long-time operation it won't be able to process
UI requests. This is also known as Not Responding.
Use ThreadPool like this:
CancellationTokenSource ct;//instantiate it before ThreadPool.QueueUserWorkItem line
private void operation_Click(object sender, RoutedEventArgs e)
{
ct = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(_ =>
{
var result = LongTimeOperation();//set the operation in another thread so that the UI thread is kept responding
//use the Dispatcher to "return" to the UI thread
Dispatcher.BeginInvoke(new Action(() =>
{
//Use result for example : Label1.Text = result.ToString();
}));
});
}
To give user a choice to be able to cancel the operation use CancellationTokenSource like this:
private void cancel_Click(object sender, RoutedEventArgs e)
{
if (ct != null)
{
ct.Cancel();
ct= null;
}
}
Note: in LongTimeOperation() you must have one more parameter of type CancellationToken
private float LongTimeOperation(CancellationToken ct)
{
if (ct.IsCancellationRequested)
return -1;
....
....
}
This link is useful about Cancellation in Managed Threads.
this is a common problem.But in WPF and WinForm, i'd like to use BackGroundWorker. See Here

Cross thread error, but not using threads

I keep getting
Cross-thread operation not valid: Control 'keyholderTxt' accessed from a thread other than the thread it was created on.
on various controls on various forms in a project, and I have googled it and found lot's of responses about how to access stuff from various threads, but as far as I know, i'm not using any other threads in my project, and to change the hundreds of possible places in the code would be unmanageable.
It never used to happen, only since I added various code that seems unrelated. I include a sample of places where I get the errors below, but it has occurred in so many places all over the solution.
keyholderTxt.Text = "Keyholders Currently In:\r\n \r\n Nibley 1: + keyholders";
or this, a better example, as you can see everything that happends from the form loading until the error:
private void Identification_Load(object sender, System.EventArgs e)
{
_Timer.Interval = 1000;
_Timer.Tick += new EventHandler(_Timer_Tick);
_Timer.Start();
txtIdentify.Text = string.Empty;
rightIndex = null;
SendMessage(Action.SendMessage, "Place your finger on the reader.");
if (!_sender.OpenReader())
{
this.Close();
}
if (!_sender.StartCaptureAsync(this.OnCaptured))
{
this.Close();
}
}
void _Timer_Tick(object sender, EventArgs e)
{
this.theTime.Text = DateTime.Now.ToString();
}
private void OnCaptured(CaptureResult captureResult)
{
txtIdentify.Clear();
//other stuff after the cross thread error
}
Can things like not closing datareaders cause this kind of error?
I am using a Windows Forms Application.
I suspect the culprit is this:
if (!_sender.StartCaptureAsync(this.OnCaptured))
I don't know the API you're using, but based on the name, I think the callback method (OnCaptured) is called on a worker thread, not the UI thread. So you need to use Invoke to perform the action on the UI thread:
private void OnCaptured(CaptureResult captureResult)
{
if (InvokeRequired)
{
Invoke(new System.Action(() => OnCaptured(captureResult)));
return;
}
txtIdentify.Clear();
// ...
}
Okay, scratch this. I see you're using System.Windows.Forms.Timer which, as the comment below mentions, already executes on the UI thread. I was thinking you were using System.Timers.Timer.
Wrong answer
The timer callback is executing on a threadpool thread. You can make it execute on the UI thread by setting the SynchronizingObject:
_Timer.Interval = 1000;
_Timer.Tick += new EventHandler(_Timer_Tick);
_Timer.SynchronizingObject = this;
_Timer.Start();
Have you checked the thread panel in VS?
The callback from _Timer (void _Timer_Tick(object sender, EventArgs e))is occurring on a background thread. Make sure you use a System.Windows.Forms.Timer (assuming you are using windows forms) if you want the callback to be on the UI thread.
As commenters have suggested. Check the thread window in your debugger to check what thread the exception is occurring on.
Alternatively, for windows forms, try this
void _Timer_Tick(object sender, EventArgs e)
{
this.BeginInvoke(new Action(() => this.theTime.Text = DateTime.Now.ToString()));
}
And for WPF, try this
void _Timer_Tick(object sender, EventArgs e)
{
this.Dispatcher.BeginInvoke(new Action(() => this.theTime.Text = DateTime.Now.ToString()));
}
And if this is not a control or window and you are in WPF
void _Timer_Tick(object sender, EventArgs e)
{
System.Windows.Application.Current.Dispatcher.BeginInvoke(new Action(() => this.theTime.Text = DateTime.Now.ToString()));
}

Showing a progress bar that updates while creating a lot of controls

I've written a piece of code that creates a lot of controls and layouts them on a canvas to visualize a tree. Now this code can take a lot of time, especially since it sometimes has to query an external service to see if there are more child node.
So I would like to show a progress bar while this code is executing. For other parts of my program I use a background worker that reports progress. However since I have to create controls that are later interact-able I don't see how to use a background worker or other threading solution here.
Since this is WPF, I also can't call Application.DoEvents(). So my question is, how can I create a lot of controls while still being able to periodically update the visual part of the GUI?
For my other code I use an Adorner that I layout over the busy piece of my app, I would prefer a solution where I can keep using that, I would also still prefer a solution using BackgroundWorker, but I'm pretty sure that is not possible.
I've looked at other SO topics, but I can't find a good answer so far
Creating controls in a non-UI thread
Creating a WinForm on the main thread using a backgroundworker
Edit:
According to this MSDN article http://msdn.microsoft.com/en-us/magazine/cc163328.aspx the BackgroundWorker should automatically invoke asynchronously on the UI thread if required, but this is not the behaviour I'm seeing, since I still see a cross thread exception.
Edit2: nvm, that's not totally true: BackgroundWorker still needs to call Invoke?
Edit3: After some more reading and some tips, this is the solution I've come to. Anybody got any tips/hints?
// Events for reporting progress
public event WorkStarted OnWorkStarted;
public event WorkStatusChanged OnWorkStatusChanged;
public event WorkCompleted OnWorkCompleted;
private BackgroundWorker worker;
private delegate void GuiThreadWork(object state);
private PopulatableControlFactory factory = new PopulatableControlFactory();
public Canvas canvas;
public void PerformLayout(TreeNode node)
{
OnWorkStarted(this, "Testing");
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.RunWorkerAsync(node);
}
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
OnWorkCompleted(this);
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var workTuple = (Tuple<GuiThreadWork, TreeNode>)e.UserState;
workTuple.First.Invoke(workTuple.Second); //Or begin invoke?
if (OnWorkStatusChanged != null)
OnWorkStatusChanged(this, e.ProgressPercentage);
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
TreeNode node = (TreeNode)e.Argument;
Thread.Sleep(1000);
worker.ReportProgress(33, Tuple.New(Place(node), node));
Thread.Sleep(1000);
worker.ReportProgress(66, Tuple.New(Place(node.children[0]), node.children[0]));
Thread.Sleep(1000);
worker.ReportProgress(100, Tuple.New(Place(node.children[1]), node.children[1]));
}
private GuiThreadWork Place(TreeNode node)
{
GuiThreadWork threadWork = delegate(object state)
{
PopulatableControl control = factory.GetControl((TreeNode)state);
Canvas.SetLeft(control, 100);
Canvas.SetTop(control, 100);
canvas.Children.Add(control);
};
return threadWork;
}
In short: I use the progressChanged event of the background worker because this is always marshalled to the GUI thread. I pass it a tuple of a delegate and some state. This way I always create the control on the GUI thread and do all actions there, while still being flexible.
Generally I don't use BackgroundWorker often but I can suggest the following:
Logic for DoWork - its executed on non UI thread
get count of nodes so you can report real progress
begin building tree ( and call Invoke on UI Dispatcher so UI thread
is adding nodes) and report progress to ReportProgress as (already
added nodes)/(total count nodes) while enumerating through all nodes
in ProgressChanged simply update some ProgressBar with new value

Try codesnippet until condition is True

this is probably a nobraner, but I can't figure it out.
I've got this function which converts wav files to mp3. I call this from my button_click handle. The thing is, when the conversion is started, the rest of the code in the button_click handler continue, while the conversion is happening in a different thread.
Now, I need the rest of the code in the button_click handle so continue to try until a boolean is true, so that I know that the conversion is done before the rest of the code continues.
I've tried using Do While but it didn't seem to do the trick. Perhaps it's just me though..
Is this a client application? Sounds like a great application for BackgroundWorker.
To execute a time-consuming operation
in the background, create a
BackgroundWorker and listen for events
that report the progress of your
operation and signal when your
operation is finished.
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler
(bw_RunWorkerCompleted);
....
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync(filename);
}
static void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
string filename = (string)e.Argument;
e.Result = DoConversion(filename);
}
static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label.Text = "Done: " + e.Result.ToString();
DoSomethingWhenConversionComplete();
}
This is called a spin-wait and is not the best way to accomplish your task:
// IsConversionComplete will be set by some other thread
while(!IsConversionComplete){
Thread.Sleep(100);
}
// carry on
A much more efficient solution requires a synchronization structure like a mutex or use of events.

Categories

Resources