.NET Threading & Locks & Waiting

I am trying to keep my GUI thread responsive during long operations. These operations must be synchronous, since they are usually operations that must be performed before the requested operation is completed.

I tried to do this with a background worker, monitors, and a lock object. Essentially, I want to start a timer before starting a lengthy process, start a lengthy process in the background thread, and wait for the background thread to mean that it has finished before continuing with the dependent code. If the long process takes too much time, show the "loading ..." dialog to the user so that they know that the application has not crashed.

An example of this is the “User Clicks” button in a graphics package, loading a large image from the disk must occur before we can draw the image, and then print pi, calculated for millions of decimal places at the top.

I can’t make the loading of the image from the disk asynchronous, which would respond to the user interface, since the user could initiate another operation that ruined the state of the program (i.e., the cancel operation).

I could just change the cursor to an hourglass and do with it, but in some cases I want the user to be able to cancel the operation too - the "Loading ..." dialog with the cancel button will take care of this pretty well.


For what I initially wanted, the locking object and System.Threading.Monitor.Enter() were involved, so that the user interface thread waited for the long thread to finish, and it continues to run. If the timer fires before the end of the long thread, then the user interface thread is still available to handle the event and draw a dialog box on the screen.

The problem I am facing is that I cannot get the Background Worker to lock the object before the UI thread tries to get the lock.

Quite annoyingly, I use some kind of third-party code, which is a very black box for processing. Therefore, I can’t adapt the code for thread-friendliness and report on its progress or support for cancellation.


My question

Is there a proven way to port third-party code so that the UI thread remains responsive, and can I show the cancel dialog if necessary? - There will be several cases when long work ends almost instantly and does not require a dialog box to be displayed.


Small clarification

Why do I want to do this? Asynchronous operations are a favorite type of Windows application ...

Well, I don’t want to block every aspect of the user interface when multiple async runs, and then unlock every aspect when it is finished. I could either set the cursor or physically disable all the baptions, etc. Etc., But really, I would prefer to simply wrap the call in "some object / method, etc.", which will allow the dialog to pop up if (and only if) the operation takes a lot of time to affect the user. I would not have to worry about changes in the thread of execution, anyway (as a whole) I could support atomic operations in the code (not split into callbacks) and still have a “responsive” user interface.

I can understand why I still have not succeeded in creating a BackgroundWorker / Thread in a synchronous lock thread, but I'm worried that I will have to go along the while(true){ sleep() } track in the GUI thread than using locks.

+4
source share
4 answers

Here is how I did it:

Code for continuous operation:

  private void button1_Click(object sender, EventArgs e) { Thread thr = new Thread(LongMethod); thr.Start(); // wait for 250 ms. If the thread is not finished, we show a from in a modal way // saying something like "please wait for the operation to complete" if(!thr.Join(250)) { pleaseWaitForm.Thread = thr; pleaseWaitForm.ShowDialog(); } } 

Then in the form of "Wait"

  public Thread Thread {get;set;} private void PleasWait_FormClosing(object sender, FormClosingEventArgs e) { // do not allow closing of the form while thread is running if (this.Thread.IsAlive) e.Cancel = true; } public void JoinAndClose(){ // this is a method that allows closing by waiting for the thread to finish this.Thread.Join(); Close(); } 

And finally, in the long method:

 private void LongMethod(){ // Do some stuff Threading.Thread.Sleep(100000); // Actually close the "please wait" window pleaseWaitForm.Invoke(new Action(()=>pleaseWaitForm.JoinAndClose())) } 

This is a bit sketchy and probably has some errors, but the general idea is simple - do timed Join() to show only the dialog if the operation is long, and close the dialog from the longest thread.

+2
source

Before continuing, I will seriously consider the BackgroundWorker class in .NET. You said you used a "background worker", so I'm not sure that this is exactly what you had in mind. It has the ability to return to your user interface with a progress notification from your work function. With progress notifications, it should significantly reduce your need for synchronization objects.

+6
source

I would put your worker in a separate class and run it on my own thread and use callbacks to signal your interface about progress and completion. The user interface can use a callback to update the progress bar and enable other controls when the workflow completes. Callbacks can also return values ​​so you can use them to terminate an employee.

Here is a very simple example:

 public delegate void CallbackDelegate(string messageArg); class Program { static void Main(string[] args) { Worker worker = new Worker(); worker.Callback = new CallbackDelegate(WorkerStatus); Thread thread = new Thread(new ThreadStart(worker.DoSomething)); thread.IsBackground = true; thread.Start(); Console.ReadLine(); // wait for enter key } static void WorkerStatus(string statusArg) { Console.WriteLine(statusArg); } } public class Worker { public CallbackDelegate Callback { private get; set; } public void DoSomething() { for (int i = 0; i < 10; i++) { Callback(i.ToString()); } Callback("done"); } } 
+1
source

I assume these are window shapes? A cheap way to get responsiveness when actually blocking is with a template like this:

 // Method call would be foo.DoWork() Action f = foo.DoWork; var asyncresult = f.BeginInvoke(null, null); while (!asyncresult.IsCompleted) Application.DoEvents(); 

In this case, the user can click the button to cancel the operation. Please note that it is generally not recommended to cancel the stream. This can lead to chaos in any condition that your program can run. The best idea is to regularly check to see if any bool undo field is set to true, and then gracefully complete the operation at the next possible moment.

0
source

Source: https://habr.com/ru/post/1301592/


All Articles