I'm coding a WinForm component where I start a Task to do the actual processing and trap the exception on a continuation. From there I want to show the exception message on a UI element.
Task myTask = Task.Factory.StartNew (() => SomeMethod(someArgs));
myTask.ContinueWith (antecedant => uiTextBox.Text = antecedant.Exception.Message,
TaskContinuationOptions.OnlyOnFaulted);
Now I get a cross-thread exception because the task is trying to update a UI element from a, obviously, non UI thread.
However, there is no Invoke or BeginInvoke defined in the Component class.
How to proceed from here?
UPDATE
Also, please note that Invoke/BeginInvoke/InvokeRequired are not available from my Component-derived class since Component doesn't provide them.
You could just add a property to your component, allows the client to set a form reference that you can use to call its BeginInvoke() method.
That can be done automatically as well, preferable so nobody can forget. It requires a bit of design time magic that's fairly impenetrable. I didn't come up with this by myself, I got it from the ErrorProvider component. Trusted source and all that. Paste this into your component source code:
using System.Windows.Forms;
using System.ComponentModel.Design;
...
[Browsable(false)]
public Form ParentForm { get; set; }
public override ISite Site {
set {
// Runs at design time, ensures designer initializes ParentForm
base.Site = value;
if (value != null) {
IDesignerHost service = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (service != null) this.ParentForm = service.RootComponent as Form;
}
}
}
The designer automatically sets the ParentForm property when the user drops your component on a form. Use ParentForm.BeginInvoke().
You can use delegates to do this.
delegate void UpdateStatusDelegate (string value);
void UpdateStatus(string value)
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new UpdateStatusDelegate(UpdateStatus), new object[]{value});
return;
}
// Must be on the UI thread if we've got this far
statusIndicator.Text = value;
}
Related
I am in need of help.
I have created a dockable WPF within Revit.
It is working well and I can 'show' & ;hide' from push buttons.
My aim is to create buttons within the WPF that run custom commands.I dont need to interact or show any information within the WPF, its purely just acting as a push button but in the WPF instead of a ribbon.
The commands currently work and can be executed via the Add-In Manager.
Below is the command I am trying to run:
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using System.Collections.Generic;
using System.Linq;
namespace Adams.Commands
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class PrecastDisallowJoin : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var uiApplication = commandData.Application;
var application = uiApplication.Application;
var uiDocument = uiApplication.ActiveUIDocument;
var document = uiDocument.Document;
// Prompt the user to select some walls
var references = uiDocument.Selection
.PickObjects(
ObjectType.Element,
new WallSelectionFilter(),
"Please select walls");
var components = references.Select(r => document.GetElement(r)).ToList();
// Start a transaction
using (Transaction t = new Transaction(document, "Change Wall Join Behavior"))
{
t.Start();
// Loop through the selected walls and change their join behavior
foreach (Reference reference in references)
{
Wall wall = document.GetElement(reference) as Wall;
WallUtils.DisallowWallJoinAtEnd(wall, 0);
WallUtils.DisallowWallJoinAtEnd(wall, 1);
}
// Commit the transaction
t.Commit();
}
return Result.Succeeded;
}
public class WallSelectionFilter : ISelectionFilter
{
public bool AllowElement(Element elem)
{
//return elem is FamilyInstance;
return elem.Name.Contains("Precast");
}
public bool AllowReference(Reference reference, XYZ position)
{
return true;
}
}
}
}
My XAML.cs looks like this:
using Autodesk.Revit.UI;
using System.Windows.Controls;
using Adams.Commands;
using System.Windows;
namespace Adams.ui
{
public partial class Customers : UserControl
{
public UIDocument uIDocument { get; }
public ExternalCommandData commandData { get; }
public Customers(UIDocument uIDocument )
{
InitializeComponent();
}
private void btnStartExcelElementsApp_Click(object sender, RoutedEventArgs e)
{
string message = string.Empty;
PrecastDisallowJoin precastDisallow = new PrecastDisallowJoin();
precastDisallow.Execute(commandData, ref message, null);
}
}
}
Any ideas of what i should be trying?
I'm new to creating add-ins and appreciate any help offered.
If I have missed any critical info please let me know.
Thank you all
When I tried the above it crashes Revit.
Im not sure how to pass the required information in the Execute method in the XAML.
The Revit dockable dialogue and hence your WPF form lives in a modeless context. It does not execute within a valid Revit API context. A valid Revit API context is only provided by Revit itself, within the event handlers called by Revit when specific events are raised. For instance, clicking a button to launch an add-in external command raises the IExternalCommand.Execute event.
The Building Coder shares a long list of articles on Idling and External Events for Modeless Access and Driving Revit from Outside
explaining how to gain access to a valid Revit API context from a modeless state.
You can address your task by using an external event:
Idling Enhancements and External Events
External Command Lister and Adding Ribbon Commands
External Event and 10 Year Forum Anniversary
Implementing the TrackChangesCloud External Event
Vipassana and Idling versus External Events
The question has also been discussed many times in the Revit API discussion forum, so you can check there for threads including WPF, dockable and external event.
You can use IExternalEventHandler:
public class MyExternalEvent : IExternalEventHandler
{
public void Execute(UIApplication app)
{
//do your revit related stuff here
}
public string GetName()
{
return "xxx";
}
}
Create external event:
ExternalEvent myExEvent= ExternalEvent.Create(new MyExternalEvent());
In order to effectively use the above you will have to hold reference to "myExEvent" in some ViewModelClass then you will be able to raise this event inside your xaml.cs:
ViewModelClass.TheEvent = myExEvent;
ViewModelClass.TheEvent.Raise();
EDIT: What you were trying to do is unfortunately not acceptable with revit API. WPF window displayed as dockpanel does not have access to valid revit api context. IExternalEventHandler gives you the possibility to somehow link dockpanel user interface with revit api.
I have a WPF application that uses MVVM pattern. I have controls in the window that are bound to properties in the ViewModel. I have a Play button that is bound to the Play() method via an ICommand interface implementation. As the Play() method steps through, I first change some properties to alter the UI to show the user that the app is working:
IsPlaying = true;
IsNotPlaying = false;
DurationTimer.Start();
Status = $"Playing: {_playingUrl}";
FilePreview?.FileNameSet(_playingUrl, "");
FilePreview?.FilePlayStart();
When the Play button is pressed it should disable the Play button via the IsPlaying property and enable the Stop button via the IsNotPlaying property. Also, the DurationTimer should start (which displays a timer) and the Status property. These are intended, as said, to show the user that things are happening since FilePreview?.FilePlayStart(); is a blocking method and the UI locks up while processing.
However, when the Play button is pressed the UI immediately locks and, then, once the FilePlayStart() method finishes its processing, it releases and the other items become effective.
Am I missing something?
WPF, like most of UI frameworks, updates the UI only from one single Thread (any attempt to update the UI from another Thread will raise a System.InvalidOperationException).
Now, since the UI Thread is busy executing your method, it cannot update the UI at the same time.
WPF works with "bulks" of code. Once a bulk is executed, the UI takes care of all the updates. AT THE END of the execution, not in the middle.
So, if in the Execute method (or any other method executed on the UI Thread) you set 40 times "can execute = true", "can execute = false", "can execute = true", "can execute = false", actually you won't see the Button being unabled and disabled 40 times. Instead, when the method exits, THEN the UI is updated with the last value.
So, how to solve this? The Execute method should be asynchronous.
Something like:
public class Command : ICommand
{
//ICommad implementation and other stuffs
//...
public async void Execute(object parameter)
{
await DoExecute(parameter);
}
private async Task DoExecute(object parameter)
{
//do something asynchronously...
}
}
In your specific case, FilePreview?.FilePlayStart(); should be asynchronous, and you should pass this method to the Command.
You can write a general Command:
public class Command : ICommand
{
//ICommad implementation and other stuffs
//...
//pass the execution in the constructor
public Command(Func<object, Task> execution)
{
_execution = execution;
}
private Func<object, Task> _execution;
public async void Execute(object parameter)
{
await _execution(parameter);
}
private async Task DoExecute(object parameter)
{
//do something asynchronously... like await Task.Delay(2000);
}
}
You can then use it this way in the owner of the Command:
MyCommand = new Command(async parameter =>
{
IsPlaying = true;
IsNotPlaying = false;
await FilePreview?.FilePlayStartAsync();
});
As soon as the await part is entered, the execution pass to another Thread, and the current Thread (that is the UI Thread) is free to update the UI, and you will see that the Buttons are enabled/disabled as you want.
If an async version of the method is not available, you can write:
MyCommand = new Command(async parameter =>
{
IsPlaying = true;
IsNotPlaying = false;
await Task.Run(() => FilePreview?.FilePlayStart());
});
You can't perform long running operations on the UI thread, as it will block the dispatcher until it is done processing.
In cases like this, just use async/await to free the dispatcher and allow message pumping to continue.
private async void PlayCommand()
{
IsPlaying = true;
IsNotPlaying = false;
DurationTimer.Start();
Status = $"Playing: {_playingUrl}";
await Task.Run(()=>
{
FilePreview?.FileNameSet(_playingUrl, "");
FilePreview?.FilePlayStart();
});
}
I know normally one is not supposed to touch UI elements from threads other than the UI thread, but I am new to WPF and I am wondering if my current working implementation can be improved.
I have an application that is comprised solely of a notification tray icon, and I want to update that icon from a background thread.
Here is my Program.cs entry point:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (IconHandler notify = new IconHandler())
{
notify.Display();
Application.Run();
}
}
}
This is my IconHandler.cs notification icon handler class:
class IconHandler : IDisposable
{
NotifyIcon ni;
public IconHandler()
{
ni = new NotifyIcon();
}
public void Display()
{
ni.MouseClick += new MouseEventHandler(ni_MouseClick);
ni.Icon = Resources.icon1;
ni.Visible = true;
new Thread(new ThreadStart(UpdateIcon)).Start();
}
public void UpdateIcon()
{
while (true)
{
// reference ni directly, it updates fine
}
}
public void Dispose()
{
ni.Dispose();
}
void ni_MouseClick(object sender, MouseEventArgs e)
{
// something useful
}
}
Is there anything blatantly incorrect about this? It seems a bit fishy to me - it was just my first attempt. It seems to work for what I want to do, does anyone have any suggestions for a better implementation? Will I run into lifecycle issues with this setup?
Is there anything blatantly incorrect about this? It seems a bit fishy to me - it was just my first attempt. It seems to work for what I want to do, does anyone have any suggestions for a better implementation? Will I run into lifecycle issues with this setup?
To begin with NotifyIcon is not a WPF control, but comes from the Windows Forms namespace. As such it has normal C# properties (e.g. Icon, Visible) meaning you have been able to alter the icon property in the non-UI thread without an exception being raised. If you had used a WPF controls then they have Dependency Properties and direct manipulation of Dependency Properties outside of the UI thread will cause an exception to be raised.
Will I run into lifecycle issues with this setup?
You've currently NOT created a WPF window or WPF controls. If your application develops such that you start using WPF and the UpdateIcon method is expanded to do more than you currently do and access these WPF objects then yes you will need a strategy to deal with the updates from non-UI threads.
You can hide some of this cross-threaded access using some helper methods.
Example 1 If your strategy becomes referencing WPF controls programmatically from the background thread then you can use a helper method such as this.
It first checks if the call is on the UI thread, if so then it updates the control directly, otherwise it will schedule that the method (itself) be called from the UI thread at a later point in time.
I've used BeginInvoke here so that the background thread can continue before the UI thread has actually called the method. If you want to block the background thread then use Invoke instead.
public void UpdateLabel(Label control, string text)
{
if (Application.Current.Dispatcher.CheckAccess())
control.Content = text;
else
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => UpdateLabel(control, text)), DispatcherPriority.Normal);
}
Example 2
If your strategy uses Events raised on the background thread to update the WPF controls programmatically then you can hide some of the cross-threading calls as part of raising the event, leaving the WPF update routine quite clean and simple to read.
Any event handlers of this event can be coded knowing that the call will be made from the UI thread, so no threading issues.
public void OnRaiseEvent(EventHandler handler, EventArgs args)
{
if (handler != null)
{
if (Application.Current.Dispatcher.CheckAccess())
handler(sender, new PropertyChangedEventArgs(propName));
else
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => handler(sender, args)), DispatcherPriority.Normal);
}
}
Example 3
If your future strategy fully utilizes the benefits of WPF with Binding (as opposed to programmatically updating your WPF controls), then you can embed the cross-threading code into the data-bound objects.
If for example your XAML databinds to the MyProperty property of an instance of the MyDataClass class and that class implements the INotifyPropertyChanged interface you can put the cross-threading code in the data class making it possible to update the data from any thread. Here is the example of the class:-
public class MyDataClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _myProperty;
public string MyProperty { get { return _myProperty;} set { PropertyChanged.SetValueAndNotify(this, ref _myProperty, value); } }
}
This class utilizes the SetValueAndNotify extension method on the PropertyChanged event. It is in here we hide the cross-threading code to simplify other parts of the code. Here's the definition of this extension method.
public static class PropertyChangedExtension
{
public static void SetValueAndNotify<T>(this PropertyChangedEventHandler handler, object sender, ref T destination, T source, [CallerMemberName] string propName = "notset")
{
// Is the new value different from the previous value? If there is no difference then there is nothing more to do
if (Equals(destination, source))
return;
// If we got to this point then the new value is different from the old value, so lets make the assignemnt and raise the property changed event
destination = source;
if (handler != null)
{
if (Application.Current.Dispatcher.CheckAccess())
handler(sender, new PropertyChangedEventArgs(propName));
else
Application.Current.Dispatcher.BeginInvoke(new System.Action(() => handler(sender, new PropertyChangedEventArgs(propName))), DispatcherPriority.Normal);
}
}
}
The above example uses the [CallerMemberName] attribute from C#5 to remove any typing errors in supplying the property name for the INotifyPropertyChanged arguments. If you are not using the latest then you will need to modify the getter and setter as follows:-
public string MyProperty { get { return _myProperty;} set { PropertyChanged.SetValueAndNotify(this, ref _myProperty, value, "MyProperty"); } }
You must always update UI from UI thread only, however, you can schedule some work on UI thread from background thread using dispatcher
public void Display()
{
ni.MouseClick += new MouseEventHandler(ni_MouseClick);
ni.Icon = Resources.icon1;
ni.Visible = true;
new Thread(new ThreadStart(UpdateIcon)).Start();
}
public void UpdateIcon()
{
while (true)
{
//do some long running work
Application.Current.Dispatcher.Invoke(()=>{
//update ui
});
}
}
But if you don't have long running work and you just want to do something periodically, you should use DispatcherTimer instead of loop in background thread.
The while(true) loop in your code will cause heavy CPU/resource usage. maybe add e.g. Thread.Sleep(1000) into the loop to allow for a break between updates.
The best usage of background threads is to perform the long-running work (e.g. communication with server/DB) on the background thread and once the thread completes, have the UI thread update the UI.
With BackgroundWorker:
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
// long running work
};
worker.RunWorkerCompleted += (sender, args) =>
{
// Update UI
};
worker.RunWorkerAsync();
async/await pattern:
public async void DoWork()
{
// Do long running task
var data = await Task.Run(() => new object());
// Update UI here
}
TaskFactory:
Task.Factory.StartNew(() => new Object()).ContinueWith(task => MessageBox.Show(task.Result.ToString()), TaskScheduler.FromCurrentSynchronizationContext());
If the UI needs to update on a constant loop, maybe use a timer to restart the process on a regular basis. This will save your CPU from taking a pounding.
I have a SafeInvoke Control extension method similar to the one Greg D discusses here (minus the IsHandleCreated check).
I am calling it from a System.Windows.Forms.Form as follows:
public void Show(string text) {
label.SafeInvoke(()=>label.Text = text);
this.Show();
this.Refresh();
}
Sometimes (this call can come from a variety of threads) this results in the following error:
System.InvalidOperationException occurred
Message= "Invoke or BeginInvoke cannot be called on a control until the window handle has been created."
Source= "System.Windows.Forms"
StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action)
in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16
What is going on and how do I fix it? I know as much as it is not a problem of form creation, since sometimes it will work once and fail the next time so what could the problem be?
PS. I really really am awful at WinForms, does anyone know a good series of articles that explains the whole model and how to work with it?
It's possible that you're creating your controls on the wrong thread. Consider the following documentation from MSDN:
This means that InvokeRequired can
return false if Invoke is not required
(the call occurs on the same thread),
or if the control was created on a
different thread but the control's
handle has not yet been created.
In the case where the control's handle
has not yet been created, you should
not simply call properties, methods,
or events on the control. This might
cause the control's handle to be
created on the background thread,
isolating the control on a thread
without a message pump and making the
application unstable.
You can protect against this case by
also checking the value of
IsHandleCreated when InvokeRequired
returns false on a background thread.
If the control handle has not yet been
created, you must wait until it has
been created before calling Invoke or
BeginInvoke. Typically, this happens
only if a background thread is created
in the constructor of the primary form
for the application (as in
Application.Run(new MainForm()),
before the form has been shown or
Application.Run has been called.
Let's see what this means for you. (This would be easier to reason about if we saw your implementation of SafeInvoke also)
Assuming your implementation is identical to the referenced one with the exception of the check against IsHandleCreated, let's follow the logic:
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
if (uiElement == null)
{
throw new ArgumentNullException("uiElement");
}
if (uiElement.InvokeRequired)
{
if (forceSynchronous)
{
uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
else
{
uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
}
}
else
{
if (uiElement.IsDisposed)
{
throw new ObjectDisposedException("Control is already disposed.");
}
updater();
}
}
Consider the case where we're calling SafeInvoke from the non-gui thread for a control whose handle has not been created.
uiElement is not null, so we check uiElement.InvokeRequired. Per the MSDN docs (bolded) InvokeRequired will return false because, even though it was created on a different thread, the handle hasn't been created! This sends us to the else condition where we check IsDisposed or immediately proceed to call the submitted action... from the background thread!
At this point, all bets are off re: that control because its handle has been created on a thread that doesn't have a message pump for it, as mentioned in the second paragraph. Perhaps this is the case you're encountering?
I found the InvokeRequired not reliable, so I simply use
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
Here is my answer to a similar question:
I think (not yet entirely sure) that
this is because InvokeRequired will
always return false if the control has
not yet been loaded/shown. I have done
a workaround which seems to work for
the moment, which is to simple
reference the handle of the associated
control in its creator, like so:
var x = this.Handle;
(See
http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html)
The method in the post you link to calls Invoke/BeginInvoke before checking if the control's handle has been created in the case where it's being called from a thread that didn't create the control.
So you'll get the exception when your method is called from a thread other than the one that created the control. This can happen from remoting events or queued work user items...
EDIT
If you check InvokeRequired and HandleCreated before calling invoke you shouldn't get that exception.
If you're going to use a Control from another thread before showing or doing other things with the Control, consider forcing the creation of its handle within the constructor. This is done using the CreateHandle function.
In a multi-threaded project, where the "controller" logic isn't in a WinForm, this function is instrumental in Control constructors for avoiding this error.
Add this before you call method invoke:
while (!this.IsHandleCreated)
System.Threading.Thread.Sleep(100)
Reference the handle of the associated control in its creator, like so:
Note: Be wary of this solution.If a control has a handle it is much slower to do things like set the size and location of it. This makes InitializeComponent much slower. A better solution is to not background anything before the control has a handle.
var that = this; // this is a form
(new Thread(()=> {
var action= new Action(() => {
something
}));
if(!that.IsDisposed)
{
if(that.IsHandleCreated)
{
//if (that.InvokeRequired)
that.BeginInvoke(action);
//else
// action.Invoke();
}
else
that.HandleCreated+=(sender,event) => {
action.Invoke();
};
}
})).Start();
I had this problem with this kind of simple form:
public partial class MyForm : Form
{
public MyForm()
{
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(Object sender, EventArgs e)
{
InitializeComponent();
}
internal void UpdateLabel(string s)
{
Invoke(new Action(() => { label1.Text = s; }));
}
}
Then for n other async threads I was using new MyForm().UpdateLabel(text) to try and call the UI thread, but the constructor gives no handle to the UI thread instance, so other threads get other instance handles, which are either Object reference not set to an instance of an object or Invoke or BeginInvoke cannot be called on a control until the window handle has been created. To solve this I used a static object to hold the UI handle:
public partial class MyForm : Form
{
private static MyForm _mf;
public MyForm()
{
Load += new EventHandler(Form1_Load);
}
private void Form1_Load(Object sender, EventArgs e)
{
InitializeComponent();
_mf = this;
}
internal void UpdateLabel(string s)
{
_mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; });
}
}
I guess it's working fine, so far...
What about this :
public static bool SafeInvoke( this Control control, MethodInvoker method )
{
if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
{
if( control.InvokeRequired )
{
control.Invoke( method );
}
else
{
method();
}
return true;
}
else return false;
}
I currently trying to write a component where some parts of it should run on the UI thread (explanation would be to long).
So the easiest way would be to pass a control to it, and use InvokeRequired/Invoke on it.
But I don't think that it is a good design to pass a control reference to a "data/background"-component, so I'm searching for a way to run code on the UI thread without the need of having a control available.
Something like Application.Dispatcher.Invoke in WPF...
any ideas,
thx
Martin
There's a better, more abstract way to do this that works on both WinForms and WPF:
System.Threading.SynchronizationContext.Current.Post(theMethod, state);
This works because WindowsForms installs a WindowsFormsSynchronizationContext object as the current sync context. WPF does something similar, installing it's own specialized synchronization context (DispatcherSynchronizationContext).
.Post corresponds to control.BeginInvoke, and .Send corresponds to control.Invoke.
First, in your form constructor, keep a class-scoped reference to the SynchronizationContext.Current object (which is in fact a WindowsFormsSynchronizationContext).
public partial class MyForm : Form {
private SynchronizationContext syncContext;
public MyForm() {
this.syncContext = SynchronizationContext.Current;
}
}
Then, anywhere within your class, use this context to send messages to the UI:
public partial class MyForm : Form {
public void DoStuff() {
ThreadPool.QueueUserWorkItem(_ => {
// worker thread starts
// invoke UI from here
this.syncContext.Send(() =>
this.myButton.Text = "Updated from worker thread");
// continue background work
this.syncContext.Send(() => {
this.myText1.Text = "Updated from worker thread";
this.myText2.Text = "Updated from worker thread";
});
// continue background work
});
}
}
You will need the following extension methods to work with lambda expressions: http://codepaste.net/zje4k6
You are right, it is not good to pass controls to threads. Winforms controls are single-threaded, passing them to multiple threads can cause race conditions or break your UI. Instead, you should make your thread's features available to the UI and let it call the thread when the UI is good and ready. If you want to have background threads trigger UI changes, expose a background event and subscribe to it from the UI. The thread can fire off events whenever it wants and the UI can respond to them when it is able to.
Creating this bidirectional communication between threads that does not block the UI thread is a lot of work. Here is a highly abbreviated example using a BackgroundWorker class:
public class MyBackgroundThread : BackgroundWorker
{
public event EventHandler<ClassToPassToUI> IWantTheUIToDoSomething;
public MyStatus TheUIWantsToKnowThis { get { whatever... } }
public void TheUIWantsMeToDoSomething()
{
// Do something...
}
protected override void OnDoWork(DoWorkEventArgs e)
{
// This is called when the thread is started
while (!CancellationPending)
{
// The UI will set IWantTheUIToDoSomething when it is ready to do things.
if ((IWantTheUIToDoSomething != null) && IHaveUIData())
IWantTheUIToDoSomething( this, new ClassToPassToUI(uiData) );
}
}
}
public partial class MyUIClass : Form
{
MyBackgroundThread backgroundThread;
delegate void ChangeUICallback(object sender, ClassToPassToUI uiData);
...
public MyUIClass
{
backgroundThread = new MyBackgroundThread();
// Do this when you're ready for requests from background threads:
backgroundThread.IWantTheUIToDoSomething += new EventHandler<ClassToPassToUI>(SomeoneWantsToChangeTheUI);
// This will run MyBackgroundThread.OnDoWork in a background thread:
backgroundThread.RunWorkerAsync();
}
private void UserClickedAButtonOrSomething(object sender, EventArgs e)
{
// Really this should be done in the background thread,
// it is here as an example of calling a background task from the UI.
if (backgroundThread.TheUIWantsToKnowThis == MyStatus.ThreadIsInAStateToHandleUserRequests)
backgroundThread.TheUIWantsMeToDoSomething();
// The UI can change the UI as well, this will not need marshalling.
SomeoneWantsToChangeTheUI( this, new ClassToPassToUI(localData) );
}
void SomeoneWantsToChangeTheUI(object sender, ClassToPassToUI uiData)
{
if (InvokeRequired)
{
// A background thread wants to change the UI.
if (iAmInAStateWhereTheUICanBeChanged)
{
var callback = new ChangeUICallback(SomeoneWantsToChangeTheUI);
Invoke(callback, new object[] { sender, uiData });
}
}
else
{
// This is on the UI thread, either because it was called from the UI or was marshalled.
ChangeTheUI(uiData)
}
}
}
Put the UI manipulation in a method on the form to be manipulated and pass a delegate to the code that runs on the background thread, à la APM. You don't have to use params object p, you can strongly type it to suit your own purposes. This is just a simple generic sample.
delegate UiSafeCall(delegate d, params object p);
void SomeUiSafeCall(delegate d, params object p)
{
if (InvokeRequired)
BeginInvoke(d,p);
else
{
//do stuff to UI
}
}
This approach is predicated on the fact that a delegate refers to a method on a particular instance; by making the implementation a method of the form, you bring the form into scope as this. The following is semantically identical.
delegate UiSafeCall(delegate d, params object p);
void SomeUiSafeCall(delegate d, params object p)
{
if (this.InvokeRequired)
this.BeginInvoke(d,p);
else
{
//do stuff to UI
}
}
What about passing a System.ComponentModel.ISynchronizeInvoke? That way you can avoid passing a Control.