I have a WPF control that is being handle, render and displayed in the main thread of my application. The control uploads thousands of data points into view in an object called "Layer." Here is a rough description of how the object/class hierarchy looks like:
public class WPFControl{
private List<Layer> myLayers;
public List<Layer> MyLayers{
get{ return myLayer;}
}
...
}
public class Layer{
private List<DataPoint> myDataPoints;
public List<DataPoint> MyDataPoints{
get{ return myDataPoints;}
}
...
}
public class DataPoint{
....
}
Since the creation process of this "Layer" object takes some time because of the thousands of DataPoint it has to read and upload, I am creating that layer object in a different thread. That works great and returns the Layer object very nicely. The problem is when I try to do add it to the WPF control to be displayed like this:
myWpfControl.MyLayers.Add(layerCreatedInOtherThread);
the WPF control fires this error:
The calling thread cannot access this object because a different thread owns it
I thought, ok, I can then use the dispatcher like so:
myWpfControl.Dispatcher.Invoke((Action)
(()=>{
myWpfControl.MyLayers.Add(layerCreatedInOtherThread);
})
);
But I keep getting the same error. Any ideas how I can get around this?
Using a BackgroundWorker you can run a task on another thread and then when it is completed have access to the results from the UI thread.
private System.ComponentModel.BackgroundWorker bgWorker;
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
//Start the work
bgWorker.RunWorkerAsync(null) //you can send an argument instead of null
Do the work
private void backgroundWorker1_DoWork(object sender,
DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
// Assign the result of the computation
// to the Result property of the DoWorkEventArgs
// object. This is will be available to the
// RunWorkerCompleted eventhandler.
e.Result = CreateLayerInOtherThread(); //if you sent an arg instead of null it as availalbe in e.Argument and can be cast from object.
}
Get the result once completed. This runs on the UI thread so you can update it.
private void bgWorker_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
}
else
{
// Finally, handle the case where the operation
// succeeded.
Layer myLayer = (Layer)e.Result;
myWpfControl.MyLayers.Add(myLayer);
}
}
Related
Hy,
I have a Observable Collection which is bind with a list box. I add logs to the Observable Collection. I always add the message immediately to the Observable Collecten. But the list gets only updated when the loop is finished but I want to Update it when I add one item in the for loop. This is why I use a Thread but I have a few problems.
I have a thread safe ObservableCollection:
class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = this.CollectionChanged;
if (collectionChanged != null)
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
if (dispatcherObject != null)
{
Dispatcher dispatcher = dispatcherObject.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => handler.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
handler.Invoke(this, e);
}
}
}
This is my test class:
public partial class MainWindow : Window
{
ThreadSafeObservableCollection<Animal> list = new ThreadSafeObservableCollection<Animal>();
public MainWindow()
{
InitializeComponent();
list.Add(new Animal() { Name = "test1" });
list.Add(new Animal() { Name = "test2" });
this.DataContext = list;
}
private void dsofsdkfd(object sender, RoutedEventArgs e)
{
//Version 1
Task.Factory.StartNew(() => test());
//Version2
/*
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var token = Task.Factory.CancellationToken;
Task.Factory.StartNew(() => test(), token, TaskCreationOptions.None, uiScheduler);
*/
}
public void test()
{
for (int i = 0; i < 10000; i++)
{
list.Add(new Animal() { Name = "test" + i });
System.Threading.Thread.Sleep(1);
}
}
}
See the private void dsofsdkfd(object sender, RoutedEventArgs e) function to the comment Version1.
In the beginning it works so the list updates everytime I add a item. After a few entries I get an exception:
"Information for developers (use Text Visualizer to read
this):\r\nThis exception was thrown because the generator for control
'System.Windows.Controls.ListBox Items.Count:1089' with name 'Logger'
has received sequence of CollectionChanged events that do not agree
with the current state of the Items collection. The following
differences were detected:\r\n Accumulated count 994 is different
from actual count 1089. [Accumulated count is (Count at last Reset +
Adds - #Removes since last Reset).]\r\n\r\nOne or more of the following sources may have raised the wrong events:\r\n
System.Windows.Controls.ItemContainerGenerator\r\n
System.Windows.Controls.ItemCollection\r\n
System.Windows.Data.ListCollectionView\r\n *
WpfApplication1.ThreadSafeObservableCollection`1[[WpfApplication1.Animal,
WpfApplication1, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null]]\r\n(The starred sources are considered more
likely to be the cause of the problem.)\r\n\r\nThe most common causes
are (a) changing the collection or its Count without raising a
corresponding event, and (b) raising an event with an incorrect index
or item parameter.\r\n\r\nThe exception's stack trace describes how
the inconsistencies were detected, not how they occurred. To get a
more timely exception, set the attached property
'PresentationTraceSources.TraceLevel' on the generator to value 'High'
and rerun the scenario. One way to do this is to run a command
similar to the following:\n
System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator,
System.Diagnostics.PresentationTraceLevel.High)\r\nfrom the Immediate
window. This causes the detection logic to run after every
CollectionChanged event, so it will slow down the application.\r\n"
See private void dsofsdkfd(object sender, RoutedEventArgs e) function to the comment Version2.
I also tried it with the TaskScheduler using FromCurrentSynchronizationContext.
Then it throws no exception but I have the same problem like at the beginning, so the list box refreshes only if the for each loop is finished.
How I can accomplish that the list box updates when I add an element?
Best regards
I wouldn't roll my own ObservableCollection for this. I'd just perform the .Add call on the UI thread.
public void test()
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal {Name = "test" + i};
// invoke list.Add on the UI thread
this.Dispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
Note that since you're in a Window subclass, this.Dispatcher will correspond to the dispatcher for the UI thread. If you move this logic to, say, a model or view model class, you'll need to explicitly capture the value of Dispatcher.Current on the UI thread, and pass that dispatcher manually to the background thread.
EDIT: OP asked for more information on using the Dispatcher outside of a FrameworkElement class. Here's how you would do that. The dispatcher for the UI thread is acquired on the UI thread by calling Dispatcher.CurrentDispatcher. That dispatcher is then passed directly into the background thread procedure.
public class MainWindowViewModel
{
// this should be called on the UI thread
public void Start()
{
// get the dispatcher for the UI thread
var uiDispatcher = Dispatcher.CurrentDispatcher;
// start the background thread and pass it the UI thread dispatcher
Task.Factory.StartNew(() => BackgroundThreadProc(uiDispatcher));
}
// this is called on the background thread
public void BackgroundThreadProc(Dispatcher uiDispatcher)
{
for (var i = 0; i < 10000; i++)
{
// create object
var animal = new Animal { Name = "test" + i };
// invoke list.Add on the UI thread
uiDispatcher.Invoke(new Action(() => list.Add(animal)));
// sleep
System.Threading.Thread.Sleep(1);
}
}
}
You need to maintain current dispatcher thread for the same. You must update collection in current dispatcher thread only. One way to do it is to use BiginInvoke() method of dispatcher class.
Save current dispatcher in a variable in constructor and then use it when needed.
_currentDispatcher = Application.Current.Dispatcher;
For example: We have a scenario where we popup an error window if we have an error. We need to close an Error window if error count is zero. Now if we are handling events and message in another thread (not on UI thread) then we need to save the UI thread dispatcher object and need to use it to update collection or any other action. Here I am closing Error Window. (I don't have solution ready for updating collection.)
if (ErrorNotifications.Count == 0)
_currentDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<ErrorNotificationWindow>(CloseErrorNotificationWindow), _errWindow);
Here CloseErrorNotificationWindow is method with parameter _errWindow.
private void CloseErrorNotificationWindow(ErrorNotificationWindow _errWindow)
{
if (_errWindow == null)
return;
if (_errWindow.IsActive)
_errWindow.Close();
}
In CloseErrorNotificationWindow() method you can update your collections and it should not give any exception as you would be using main UI thread to do it.
Hope this will helpful.
I've an existing WPF application, which has several sections. Every section is a UserControl, that implements an interface.
The interface specify two methods: void LoadData([...]) and bool UnloadData().
Those method are called by the UI thread, so we need to do our work in backgroundworker if it's time consuming.
No problems with LoadData since we can update the UI asynchronously. The problem is with UnloadData().
This should return if we can really leave the current view.
This is computed with the current status of data(Saved/modified/Invalid):
Saved return true,
Invalid asks if you want to stay to save some
correct data or leave without saving
Modified tell you that you can
either cancel your change(return true), either continue to
edit(return false), either save you current data(return true)
The problem is with the "Modified -> Save". This is a time consuming method, so to respect the philosophy of the application, we should run this in a background thread(with a busy indicator).
But if we just launch the thread and go to the next section, it will return "true" to the method call, and we will directly launch the next view.
In my case, loading the next view before our local data is saved can be a problem.
So:
Is there a way to wait on the background thread to finish before returning "true", WITHOUT blocking the UI?
public bool UnloadData(){
if(...){
LaunchMyTimeConsumingMethodWithBackgroundWorker();
return true;//Only when my time consuming method ends
}
//[...]
}
Important EDIT
Maybe I wasn't clear enought: I know how to use a BackgroundWorker, or TPL. My problem is that the parent class(the one which call the UnloadData()" is a class that I cannot edit(for multiple reasons: It's in another DLL that will not be reloaded, it already works with 70+ userControls, all in separate projects(dll), loaded by reflection.
This wasn't my choice, I don't find it good, but I've to deal with it now. I'm mostly looking for way to make my method wait on the return of my method. I'm not sure if it is possible. But I'm looking for a workaround, it will spare me weeks of works.
Ok now I'm excited, because I think I may have discovered something on my own...
So, what you do is this: You create a DispatcherFrame, push that frame onto the Dispatcher, and in the RunWorkerCompleted you set the Continue of the Frame to false.
This is the code so far:
public void Function()
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += TimeConsumingFunction;
var frame = new DispatcherFrame();
worker.RunWorkerCompleted += (sender, args) =>
{
frame.Continue = false;
};
worker.RunWorkerAsync();
Dispatcher.PushFrame(frame);
}
private void TimeConsumingFunction(object sender, DoWorkEventArgs doWorkEventArgs)
{
Console.WriteLine("Entering");
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
}
Console.WriteLine("Exiting");
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Function();
Console.WriteLine("Returns");
}
You should implement a dependency property "IsBusy" of type bool, that you set to TRUE before starting the BackgoundWorker, and then to FALSE when the work is complete.
On the UI, you bind to that property whatever functionality you want disabled during the processing(like the button for loading the next view, etc.); or maybe showing a "Cancel" button.
You should not "wait" for the operation to complete, you can retrieve the result in an additional variable, that the BackgroundWorker will set:
BackgroundWorker _bw;
bool _returnValue = false;
private void button_Click(object sender, RoutedEventArgs e)
{ // if starting the processing by clicking a button
_bw = new BackgroundWorker();
IsBusy = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_RunWorkerCompleted);
_bw.RunWorkerAsync();
}
void _bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IsBusy = false;
// retrieve the result of the operation in the _returnValue variable
}
void _bw_DoWork(object sender, DoWorkEventArgs e)
{
_returnValue = UnloadData();
}
private bool UnloadData()
{
if (...)
{
LaunchTimeConsumingMethod();
return true;
}
else
return false;
//etc ...
}
public bool IsBusy
{
get { return (bool)GetValue(IsBusyProperty); }
set { SetValue(IsBusyProperty, value); }
}
// Using a DependencyProperty as the backing store for IsBusy. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsBusyProperty =
DependencyProperty.Register( ... )
You may be able to try using the new "await" features of .NET 4.5.
The await keyword allows you to await the completion of a Task object, without blocking the UI.
Try this modification:
public async bool UnloadData()
{
if(...)
{
await Task.Factory.StartNew(() =>
{
LaunchMyTimeConsumingMethod();
});
return true;//Only when my time consuming method ends
}
//[...]
}
Treat UnloadData as a async operation and let the async/await features handle both the case when it completes synchronously and when it needs to complete asynchronously:
public async Task<bool> UnloadData(){
if(...){
// The await keyword will segment your method execution and post the continuation in the UI thread
// The Task.Factory.StartNew will run the time consuming method in the ThreadPool
await Task.Factory.StartNew(()=>LaunchMyTimeConsumingMethodWithBackgroundWorker());
// The return statement is the continuation and will run in the UI thread after the consuming method is executed
return true;
}
// If it came down this path, the execution is synchronous and is completely run in the UI thread
return false;
}
private async void button_Click(object sender, RoutedEventArgs e)
{
// Put here your logic to prevent user interaction during the operation's execution.
// Ex: this.mainPanel.IsEnabled = false;
// Or: this.modalPanel.Visibility = Visible;
// etc
try
{
bool result = await this.UnloadData();
// Do whatever with the result
}
finally
{
// Reenable the user interaction
// Ex: this.mainPanel.IsEnabled = true;
}
}
EDIT
If you can't modify the UnloadData, then just execute it on the ThreadPool, as #BTownTKD noted:
private async void button_Click(object sender, RoutedEventArgs e)
{
// Put here your logic to prevent user interaction during the operation's execution.
// Ex: this.mainPanel.IsEnabled = false;
// Or: this.modalPanel.Visibility = Visible;
// etc
try
{
// The await keyword will segment your method execution and post the continuation in the UI thread
// The Task.Factory.StartNew will run the time consuming method in the ThreadPool, whether it takes the long or the short path
bool result = await The Task.Factory.StartNew(()=>this.UnloadData());
// Do whatever with the result
}
finally
{
// Reenable the user interaction
// Ex: this.mainPanel.IsEnabled = true;
}
}
You probably should use TPL if your framework version is 4.0:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); // this will work only if you're running this code from UI thread, for example, by clicking a button
Task.Factory.StartNew(() => UnloadData()).ContinueWith(t => /*update ui using t.Result here*/, uiScheduler);
Hope this helps.
You have to implement a callback function (RunWorkerCompleted), this is called when the background worker finishes.
Check out an example here:
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
I'm pretty new to C# and all the threading stuff and I'm getting "Cross-threaded operation not valid error" at the moment.
Here are the relevant parts of the code:
private LinkedList<string> _statusList = new LinkedList<string>();
private void ReportToStatus(string message)
{
_statusList.AddLast(message);\
// textStatus is a textbox.
// And this is the exact line that is giving the error:
textStatus.Lines = _statusList.ToArray();
}
private void RunTest()
{
// ...
// Run the test in the background worker.
bgwTest.RunWorkerAsync(testCase);
}
private void bgwTest_DoWork(object sender, DoWorkEventArgs e)
{
TestCase testCase = e.Argument as TestCase;
// ...
// Run the test.
switch (testCase.TestType)
{
case "TestA": TestA(testCase);
break;
}
e.Result = testCase;
}
private void TestA(TestCase testCase)
{
// ...
PrintStatistic(statisticsForCoil, testCase.OutputFile);
}
}
private void PrintStatistic(int[] statistics, string outputFile)
{
// ...
ReportToStatus(result);
}
How should I proceed?
It looks like there might be a problem in _statusList. You can't write to it from a different thread, only read.
From MSDN
"The LinkedList class does not support chaining, splitting, cycles,
or other features that can leave the list in an inconsistent state.
The list remains consistent on a single thread. The only multithreaded
scenario supported by LinkedList is multithreaded read operations."
Also, you can't access the UI from a background thread. You need to use the dispatcher to invoke operations onto the UI thread. To do this your code will need to look like this
WPF
Application.Current.Dispatcher.Invoke(new Action(delegate
{
textStatus.Lines = _statusList.ToArray();
}));
WinForms
textStatus.Invoke(new Action(delegate
{
textStatus.Lines = _statusList.ToArray();
}));
The BackgroundWorker has a dedicated mechanism for updating the UI:
BackgroundWorker.ReportProgress. For example, in your code it could look like this:
private void ReportToStatus(string message)
{
_statusList.AddLast(message);
// textStatus is a textbox.
// And this is the exact line that is giving the error:
bgwTest.ReportProgress(0, _statusList.ToArray());
}
//Assuming this is the method handling bgwTest's ProgressChanged event
private void bgwTest_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textStatus.Lines = (string[])(e.UserState);
}
You are trying to update the UI from your background worker which will cause that exception. You can use the Dispatcher to schedule an update instead - or more ideally use the background worker to do "background" work only, then do your UI updates when the RunWorkerCompleted event is raised.
I have a class to launch background operations in a WinForms application. I need to write this background worker since my requisites are using .NET 1.1, so I cannot use BackgroundWorker, that is only available from .NET 2.0
This class get a delegate and execute it in a thread. I want the main thread to respond to events.
I also want to indicate that the operation is running setting the application cursor to Cursors.WaitCursor.
What do you think about current implementation? I'm interested in the method WaitTillThreadFinishes(), because I'm not sure about Application.DoEvents(), please read the code and share with me opinions about WaitTillThreadFinishes.
The following code executes the operation:
private object ExecuteOperation (Delegate target, params object[] parameters)
{
mTargetDelegate = target;
mTargetParameters = parameters;
mTargetThread = new Thread(new ThreadStart(ThreadProc));
mTargetThread.Name = mTargetDelegate.Method.Name;
mOperationFinished = false;
// start threaded operation
mTargetThread.Start();
// perform active waiting
WaitTillThreadFinishes();
return mTargetResult;
}
The following code is executed in a thread, simply call the delegate, and wrap exceptions:
protected virtual void ThreadProc()
{
try
{
mTargetResult = mTargetDelegate.DynamicInvoke(mTargetParameters);
}
catch (ThreadAbortException) { }
catch (Exception ex)
{
//manage exceptions here ...
}
finally
{
mOperationFinished = true;
}
}
And this is the code performs an active waiting. I'm interested on share with you. Any better option? Any pain calling Application.DoEvents() massively?
private void WaitTillThreadFinishes ()
{
// Active wait to respond to events with a WaitCursor
while (!mOperationFinished)
{
// sleep to avoid CPU usage
System.Threading.Thread.Sleep(100);
Application.DoEvents();
Cursor.Current = Cursors.WaitCursor;
}
Cursor.Current = Cursors.Default;
}
Thanks in advance.
Please let me know if i understood your question correctly.
Why dont you use an event to notify the UI that the worker finished his job?
This way, the UI doen't get blocked by the worker, and you avoid busy waiting.
Sample Implementation
public class MyBackgroundWorker
{
// Fields
private Delegate _target;
private object[] _arguments;
// Events
public event EventHandler RunWorkerStarted;
public event EventHandler<RunWorkerCompletedEventArgs> RunWorkerCompleted;
// Event Invocators
public void InvokeRunWorkerStarted()
{
var handler = RunWorkerStarted;
if (handler != null) handler(this, new EventArgs());
}
public void InvokeRunWorkerCompleted(object result)
{
var handler = RunWorkerCompleted;
if (handler != null) handler(this, new RunWorkerCompletedEventArgs(result));
}
public void RunWorkerAsync(Delegate target, params object[] arguments)
{
_target = target;
_arguments = arguments;
new Thread(DoWork).Start(arguments);
}
// Helper method to run the target delegate
private void DoWork(object obj)
{
_target.DynamicInvoke(_arguments);
// Retrieve the target delegate's result and invoke the RunWorkerCompleted event with it (for simplicity, I'm sending null)
InvokeRunWorkerCompleted(null);
}
}
internal class RunWorkerCompletedEventArgs : EventArgs
{
public RunWorkerCompletedEventArgs(object result)
{
Result = result;
}
public object Result { get; set; }
}
Usage
In the UI you can use it this way:
private void button1_Click(object sender, EventArgs e)
{
var worker = new MyBackgroundWorker();
worker.RunWorkerStarted += worker_RunWorkerStarted;
worker.RunWorkerCompleted += worker_Completed;
worker.RunWorkerAsync(new MethodInvoker(SomeLengthyOperation), null);
}
void worker_RunWorkerStarted(object sender, EventArgs e)
{
}
void worker_Completed(object sender, EventArgs e)
{
MessageBox.Show("Worker completed");
}
private void SomeLengthyOperation()
{
Thread.Sleep(5000);
}
Final Notes
Remember to Invoke() in the event handlers to access the UI thread correctly. You can also modify the worker so this is done in a safe way.
There isn't much support in 1.1 for doing this, but I'll tell you what I'd do (sorry, no code at this time).
As for the asynchronous operation, I'd use the APM to kick off and complete the asynchronous method. This is fully supported in 1.1, so no worries there.
The idea is that in the UI, you store some indication that work is being done (a boolean field, for example) and (optionally) a Timer used to "wake up" the UI on a regular basis to check on the current status of the background work and indicate this to the user.
You would set the boolean to indicate you are working in the background, call BeginInvoke() on your delegate (using the overload that takes a callback search for "Executing a Callback Method When an Asynchronous Call Completes
"), and start the Timer. When the user attempts to use the UI, you would optionally check the boolean and cancel the operation, thus preventing the user from doing something harmful while you are waiting. When the timer Ticks, you can check the status of your asynchronous method by, say, a shared field that the method writes updates to and the UI reads. For example, a double which the UI uses to update a progress bar.
Once the callback fires, you clean up your asynchronous mess (i.e., call EndInvoke, and handle any exceptions thrown, etc), turn off the Timer and reset your boolean running indication field.
By using this method, you can keep the UI completely responsive (and partially usable, depending on your overall design), can set up a mechanism to abort the background worker (through the use of another field, the reverse of the boolean mentioned earlier, and inform the user of the status of the operation.
There is occasionally a case for kicking off a thread and waiting for its return, if you are doing other things in the meantime, but in this case, with the code you have shown, it is meaningless.
If you want the threadProc to allow for events to be processed, then call doevents in that, which will free up the CPU briefly, allowing for processing.
Unless you have a particular reason for needing to thread processes, you should not do it. Getting it right - as Ian Boyd has said - is difficult, and the more you need to interact with it the harder it is. If you can run fire-and-forget threads, that is the easiest.
Ideally you start the asynchronous operation and leave your form alone (aside from maybe using the Cursors.AppStarting cursor).
When your threaded operation completes, it then needs to fire some sort of BackgroundOperationComplete event. This is where your would call from your asynchronous delegate code:
form.Invoke(BackgroundOperationComplete);
The form's BackgroundOperationComplete method is where you can handle the fact that the background operation is complete:
void BackgroundOperationComplete()
{
this.Cursor = Cursors.DefaultCursor;
lblAnswer.Text = "The thread is done";
}
If all else fails, keep the operation synchronous, and use an IProgressDialog. (brief conceptual pseudo-code from memory):
void DoStuff()
{
IProgressDialog pd = new ProgressDialog();
pd.SetTitle = "Calculating Widgets";
pd.StartTimer(PDTIMER_RESET, NULL)
pd.StartProgressDialog(this.Handle, NULL, PROGDLG_MODAL | PROGDLG_NOTIME | PROGDLG_NOPROGRESSBAR | PROGDLG_NOCANCEL, NULL);
try
{
pd.SetLine(1, "Please wait while the widgets are frobbed");
DoTheThingThatDoesTheSynchronousStuff();
}
finally
{
pd.StopProgressDialog();
}
pd = null;
}
I've got the following code:
void ReferenceManager_DoWork(object sender, DoWorkEventArgs e)
{
try
{
// Get the raw data
byte[] data = this.GetData(IvdSession.Instance.Company.Description, IvdSession.Instance.Company.Password);
// Deserialize the list
List<T> deseriaizedList = null;
using (MemoryStream ms = new MemoryStream(data))
{
deseriaizedList = Serializer.Deserialize<List<T>>(ms);
}
// Convert the list to the Reference Interface
List<IReference> referenceList = new List<IReference>();
foreach (T entity in deseriaizedList)
{
IReference reference = (IReference)entity;
referenceList.Add(reference);
}
e.Result = referenceList;
}
catch (WebException)
{
e.Result = null;
}
}
The code is basically calling a delegate to a WebService method. Unfortunately, the main reason I used a background worker was to stop the UI freezing when loading the data. I've got a form that pops up saying please wait, click here to cancel.
On the click of cancel I call CancelAsync on the background worker. Now, as I'm not looping I cannot see a nice way of checking for a cancellation. The only option I can see is to have...
byte[] m_CurrentData;
...outside of the method and launch a new thread on the start of DoWork(..) that calls the webservice to populate m_CurrentData. I'd then need to perform a loop checking if cancelled or checking if m_CurrentData is no longer null.
Is there any better way of achieving a cancellation?
The actual work seems to be done inside the this.GetData(...) method which is not shown. I gather it is calling a webservice. You probably should call the Abort() method on the proxy object to stop the client from waiting for the response. There is no point in calling CancelAsync(), just be sure to correctly check for errors in RunWorkerCompleted(). The easiest way is probably not to catch any exceptions in _DoWork(), but check the Result.Error property in Completed(). You should do that anyway.
Just to clarify, the CancelAsync() method is only useful to stop a loop inside DoWork(). You are not running a (meaningful) loop there, so another approach is needed.
Update
I've just checked the MSDN for DoWorkEventArgs and realised that my previous answer was wrong. There's the CancellationPending property on the BackgroundWorker which is set by by the call to CancelAsync (from the MSDN). Thus your DoWork method can become:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Do not access the form's BackgroundWorker reference directly.
// Instead, use the reference provided by the sender parameter.
BackgroundWorker bw = sender as BackgroundWorker;
// Extract the argument.
int arg = (int)e.Argument;
// Start the time-consuming operation.
e.Result = TimeConsumingOperation(bw, arg);
// If the operation was canceled by the user,
// set the DoWorkEventArgs.Cancel property to true.
if (bw.CancellationPending)
{
e.Cancel = true;
}
}
Can you use this?