I'm writing a addin for outlook which has some network code for API calls, which is why I have several classes extending the BackgroundWorker class, each encapsulating a API call. The code looks like this for a Api Call:
public class ApiLogin : BackgroundWorker
{
private void ThisAddInStartup(object sender, EventArgs e)
{
this.DoWork += BgWorkerDoWork;
this.RunWorkerCompleted += BgWorkerCompleted;
}
private void BgWorkerDoWork(object sender, DoWorkEventArgs e)
{
//Perform network call on the background thread
var logged_in = ApiRequests.login();
e.Result = logged_in;
}
//This should run in the main thread, no?
private void BgWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var logged_in = (bool)e.Result;
//Do stuff in main thread, hopefully..
//Investigate if this runs in the main thread since it should block Outlook, no?
Thread.Sleep(50000);
}
}
And the code looks like this for my Outlook Addin:
public class ThisAddin
{
private ApiLogin _loginWorker;
private void ThisAddInStartup(object sender, EventArgs e)
{
_loginWorker = new ApiLogin();
_loginWorker.RunWorkerAsync();
}
}
When I run my addin I expect outlook to block for 50 seconds since I have a Thread.Sleep(50000) in the background workers Completed-event handler, but this does not happen. This implies to me that this code does not run in the main thread? I have searched for a solution in vain and now I would like to know if anyone here knows what might be the problem?
BackgroundWorker requires a synchronization provider to determine on which thread the RunWorkerCompleted event runs. It uses SynchronizationContext.Current. Odds are very high that this property is null when your plugin starts. So nothing is getting synchronized and the event runs on a threadpool thread.
There are two synchronization providers in the .NET framework, respectively the one for Winforms and the one for WPF. They need their respective message loop to do the thread marshaling, they assign SynchronizationContext.Current in their Application.Run() method. You have neither. The simplest solution is to create a Winforms Form and call its ShowDialog() method. That in itself already blocks the Outlook user interface. Also good to provide some feedback to the user so she doesn't have to guess why Outlook stopped responding.
I would certainly expect it to run on the same thread as the ThisAddinStartup method: you can verify this by tracing the thread id in both places.
As for Outlook, maybe it's running your add-in on a separate UI thread.
Related
In my WPF program it took huge processing time and freezing for long time.
so I decided to use background worker and process it in background.
but it does not work. through debug, the program stop at Render3D(). It does not throw exception. Its like when you put return.
In other word it does nothing after reaching Render3D() and will just return.
(I don't say it will return Because im not sure but the behavior is same as return)
private readonly BackgroundWorker backgroundWorker = new BackgroundWorker();
private AssetDeclaration _assetDeclaration = new AssetDeclaration();
public MainWindow()
{
backgroundWorker.DoWork += backgroundWorker1_DoWork;
backgroundWorker.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 1000; i++)
{
if (!((BackgroundWorker)sender).CancellationPending)
{
Render3D(); // will return at this point. (why?) or waiting for something to start?
((BackgroundWorker)sender).ReportProgress(i);
}
else
{
e.Cancel = true;
break;
}
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Done!");//will show message box instant.
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBar1.Value = e.ProgressPercentage;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//...Some work here before starting Hard job!
//...From now i want to start heavy process in background.
//...with report to progress bar at same time.
backgroundWorker.RunWorkerAsync(100);
}
Render3D() works fine without Background processing but will freeze for some time.
Render3D() is in Partial class of MainWindow .because there are lots of methods so i decided to separate them.
Also how can I use ReportProgress outside backgroundWorker1_DoWork . for example in Render3D().
Last thing : i want to know how to show the user how much of process is done.
Solved!:
The problem was because i set Viewport3D inside Render3D()
I separated it from Render3D and problem got fixed. thanks to Henk Holterman for the right answer.
It seems some tasks cant be done in another Thread. with the Error report i find out that the invalid task is setting Viewport3D properties.
this tasks must be done in Main thread.
below is invalid Code that made background worker stop functioning.
DefineCamera();
Viewport.Children.Add(model); // Must be run in Main thread.
And this Part.
private void DefineCamera()
{
PerspectiveCamera camera = new PerspectiveCamera
{
FieldOfView = 60
};
PositionCamera(camera);
Viewport.Camera = camera; // Must be run in Main thread.
}
First of all, you had trouble finding the error.
... the program stop at Render3D(). It does not throw exception. Its like when you put return.
What actually happened was that an exception was thrown by your method and was captured by the Backgroundworker. It is transferred to the Completed event but you do have to act on it there.
private void worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
// check error, check cancel, then use result
if (e.Error != null)
{
// handle the error
}
else if (e.Cancelled)
{
// handle cancellation
}
else
{
// use the result(s) on the UI thread
}
// general cleanup
}
Failing to look at either e.Error or e.Result is the same as having an empty catch{} block in your program.
And with error handling in place we then have
oh yes it shown Error. System.InvalidOperationException the calling thread cannot access this object because a different thread owns it
This indicates that your Render3D() still interacts with the GUI somewhere.
The basic advice is to separate all the calculation (and I/O, database) work from the UI work. You can run the CPU bound and I/O bound cod in a thread but the GUI is single threaded, you can only interact with it from the main Thread.
In the world of WPF, unlike Windows Forms that you were used to, you should consider Dispatcher. To do this, you have to import System.Windows.Threading
private void ThreadTask()
{
Thread.Sleep(TimeSpan.FromSeconds(5));
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate()
{
//Do some heavy task here...
});
}
Quick Update
In order to run the thread from a button click or whatever, any function, add this line of code:
Thread thread = new Thread(new ThreadStart(ThreadTask));
thread.Start();
This line of code is equivalent to BackgroundWorker.RunWorkerAsync();
I would highly recommend using async/await. This feature was introduced in .NET 4.5, and is used to shift work off the main WPF GUI thread to make the application fast and responsive.
Essentially, the rule is to push any calculations which do not interact with GUI onto a background thread using a combination of Task.Run and async/await. Together with Dispatcher.Invoke, you don't really need anything else.
For example, a slow data call that might fetch data from the database could be pushed onto a background thread, so the application does freeze while it waits for the SQL to execute.
I've used this to make the applications that I write fast, responsive and snappy.
I've made myself a class that reads RSS feeds and returns them to my main class. The code that I use for this is:
public List<Post> getLatestPosts()
{
this.rssReader = new XmlTextReader(this.rssUrl);
this.rssDoc = new XmlDocument();
// Load the XML content into rssDoc
rssDoc.Load(rssReader);
// ... other code to parse XML ... //
}
Now, when I call getLatestPosts() my application locks up for a few seconds. I'm assuming it's because that's how long it takes for the application to request the RSS feed (network latency and so forth).
I want to change this so my program doesn't lock up, and instead just waits for the response. I had the idea of using threads in my main form, but I'm confused about how I can capture whatever RSS data getLatestPosts() gets.
If I do this in my button click on my main form:
private void bGetLatestPosts_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(rssReader.getLatestPosts()));
}
I'm not capturing anything that getLatestPosts() returns.
I'm completely new to threads (this is mostly just me messing around to try and learn them) but I do have some experience in C#.
Thanks.
I'm assuming you are writing a WinForms application. There are two important rules for writing responsive WinForms applications:
Everything you do on the do on the main UI thread should complete quickly.
You can't access controls from any thread other than the main thread.
To solve your problem, run your code in a BackgroundWorker.
The BackgroundWorker class allows you to run an operation on a separate, dedicated thread. Time-consuming operations like downloads and database transactions can cause your user interface (UI) to seem as though it has stopped responding while they are running. When you want a responsive UI and you are faced with long delays associated with such operations, the BackgroundWorker class provides a convenient solution.
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. You can create the BackgroundWorker programmatically or you can drag it onto your form from the Components tab of the Toolbox. If you create the BackgroundWorker in the Windows Forms Designer, it will appear in the Component Tray, and its properties will be displayed in the Properties window.
To set up for a background operation, add an event handler for the DoWork event. Call your time-consuming operation in this event handler. To start the operation, call RunWorkerAsync. To receive notifications of progress updates, handle the ProgressChanged event. To receive a notification when the operation is completed, handle the RunWorkerCompleted event.
Here's some code that might help you:
private void bGetLatestPosts_Click(object sender, EventArgs e)
{
bGetLatestPosts.Enabled = false;
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = getLatestPosts();
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// Handle exception...
}
else
{
List<Post> result = (List<Post>)e.Result;
// Update GUI...
}
bGetLatestPosts.Enabled = true;
}
While maintaining some code, I discovered that we have an infinite hang-up in a background worker. The worker requires access to a script file. The original code was written to pop up a file dialog if no script file was defined, to allow the user to select one. It looks something like this:
private void bgworker_DoWork(object sender, DoWorkEventArgs e)
{
... snip ...
if (String.IsNullOrWhitespace(scriptFile))
{
scriptFile = PromptForScript();
}
... snip ...
}
private string PrompForScript()
{
string script = "";
OpenFileDialog openDialog = new OpenFileDialog();
if (openDialog.ShowDialog() == DialogResult.OK)
{
script = openDialog.FileName;
}
return script;
}
I've read up a bit about MethodInvoker, but almost all of the invoke methods require that you call them from a control. The background worker in question is running from a separate class, which doesn't extend Control. Do I use the form that calls the class with the bgworker for that? Or is there another way of interrupting the thread for user input?
It's not recommended to invoke the UI from the background worker DoWork event handler. BackgroundWorker is meant to do work on a non-UI thread to keep the UI responsive. You should ask for any file information before starting the BackgroundWorker object with RunWorkerAsync.
What you want to do is capture the SynchronizationContext on the UI thread and pass that along to the background worker. The BackgroundWorker can call Send() (synchronous, like Invoke) and Post() (asynchronous, like BeginInvoke) on the context to invoke back to the correct UI thread. That said, there is probably no need for the BackgroundWorker in this case- a regular threadpool thread would do just fine.
This (slightly modified) block of code from http://msmvps.com/blogs/manoj/archive/2005/11/03/74120.aspx should give you the general idea:
private void button1_Click(object sender, EventArgs e)
{
// Here we are on the UI thread, so SynchronizationContext.Current
// is going to be a WindowsFormsSynchronizationContext that Invokes properly
ctx = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(
// This delegate is going to be invoked on a background thread
s => {
// This uses the context captured above to invoke
// back to the UI without the "messy" referencing
// of a particular form
ctx.Send(s2 =>
{
// Interact with your UI here- you are on the UI thread
},null);
}
);
}
If some Form kicks off a long-running process within another class that uses a BGworker, why wouldn't the form (or presenter, depending on UI architecture) handle the processing of the error state?
Perhaps, just pass back some status result (or throw a very targeted, specific exception that you can handle in the UI)?
Leave the background worker to determine if there IS an error, but leave handing the error (especially the UI portion of showing a message box) to the upper layers.
Sorry this didn't have more concrete code but it could go a lot of different ways depending on how your system is architected.
Well, the Form class has an Invoke method, so passing the form instance to the background working class should work.
I'm trying to load loadingForm like below code. But it doesn't work, the loadingForm doesn't disappear, the event RunWorkerCompleted doesn't get called.
And also, I need to call loadingForm and backgroundWorker multiple times, so how do I completely dispose the loadingForm and the backgroundWorker after each call?
I think that there're many things wrong in my code but I don't know how to fix it. Could you show me how to solve my problem and point out where I need to fix? Thanks a lot in advance.
public partial class loginForm : Form
{
//....
private loadingForm lf;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
lf.Show();
While (backgroundWorker1.isBusy)
Application.DoEvents();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, DoWorkEventArgs e)
{
lf.Close();
}
private void connect()
{
//....
Thread mainThread = new Thread(ThreadStart(listentoServer));
mainThread.Start();
}
private void listentoServer()
{
//....
lf = new loadingForm();
backgroundWorker1.RunWorkerAsync();
//....
backgroundWorker1.CancelAsync();
//....
}
}
There's a lot of things wrong with your code. If you can, try to take a step back and describe what exactly you want to do.
BackgroundWorker uses the Event-based Asynchronous Pattern (EAP). As such, it requires a thread context in which to live. UI threads satisfy this requirement, but manually-created Thread instances do not (unless you install one or make the instance a secondary UI thread).
Similarly, UI components bind to a particular thread. They require an STA thread that does message pumping (e.g., Application.DoEvents).
It looks to me like you're creating a manual Thread and then creating UI components from that thread (so you know that the thread should be STA and include a message pumping loop, neither of which are in your code). Then that thread starts a BGW which does message pumping.
It's not clear what you're trying to accomplish here - maybe displaying a dialog in a separate thread?
Multiple UI threads in a WinForms app is not an officially supported scenario AFAIK, though some people have gotten it working. I've never seen a need for it, though.
According to what you have shown (which is admittedly incomplete, so this may not be the problem), you are not hooking up your event to the backgroundWorker_DoWork and backgroundWorker_RunWorkerCompleted event handlers. Somewhere (after you instantiate your backgroundWorker), you should have this:
backgroundWorker.DoWork += new EventHandler(backgroundWorker_DoWork);
backgroundWorker.RunWorkerCompleted += new EventHandler(backgroundWorker_RunWorkerCompleted);
As a disclaimer, this was written by hand, so the event names or EventHandler types may be incorrect.
i really don't know how to fix your code definitively, or if your code even works the way you have it, i can only give you the following guidance.
use CancellationPending property of background worker, not the IsBusy property
when working with windows forms and threaded code, always use the Invoke/BeginInvoke methods to make sure you marshal your call back to the thread that the control originated from.
I believe I have a potential threading issue. I have a user control that contains the following code:
private void btnVerify_Click(object sender, EventArgs e)
{
if (!backgroundWorkerVerify.IsBusy)
{
backgroundWorkerVerify.RunWorkerAsync();
}
}
private void backgroundWorkerVerify_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
VerifyAppointments();
}
private void backgroundWorkerVerify_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Information was Verified.", "Verify",
MessageBoxButtons.OK, MessageBoxIcon.Information);
CloseEvent();
}
vanilla code. but the issue I have is that when the application is running and the users tabs to another application when they return to mine the application is hung, they get a blank screen and they have to kill it. This started when I put the threading code. Could I have some rogue threads out there? what is the best way to zero in a threading problem? The issue can't be recreated on my machine...I know I must be missing something on how to dispose of a backgroundworker properly. Any thoughts are appreciated, Thanks,
Jose
Your code snippet doesn't explain it, but deadlocking the UI thread is never that difficult when you use BGW and are interested in its IsBusy property. A deadlock like this is usually easy to diagnose, use Debug + Break All. Then Debug + Windows + Threads and double-click the Main Thread. Then Debug + Windows + Call Stack to see what the UI thread is doing.
The common scenario is that the UI thread is looping on the IsBusy property. The BGW can't complete because its RunWorkerCompleted event can't run until the UI thread goes idle.
Are you accessing the GUI from the VerifyAppointments() method? You should utilize the DoWorkEventArgs in order to pass in the arguments you are verifying and you should not access the GUI from the BackgroundWorker directly.
You can safely access the GUI only in the RunWorkerCompleted or the ProgressChanged events.