'System.Windows.Threading.Dispatcher and WinForms?
Does a System.Windows.Threading.Dispatcher work on the UI-thread of a WinForms application?
If yes, why? It is coming from WindowsBase.dll which seems to be a WPF component.
If not, how can I invoke work units back onto the UI-thread? I've found Control.BeginInvoke(), but it seems clumsy to create a control only to reference the originating thread.
Solution 1:[1]
You can use Dispatcher even in a WinForms app.
If you are sure to be on a UI thread (e.g. in an button.Click handler), Dispatcher.CurrentDispatcher gives you the UI thread dispatcher that you can later use to dispatch from background threads to the UI thread as usual.
Solution 2:[2]
Dispatcher is a WPF component, not a WinForms component.
If you want to dispatch work items on the UI thread, then you would have to either use Control.BeginInvoke as you've already found, or react to ResetEvents/WaitObjects across threads.
Usually invoking work items on the UI thread is a bad thing unless it's a UI piece of work (ie. updating a control's content or something) in which case the Control.BeginInvoke() would be sufficient.
Solution 3:[3]
I provided an example of using System.Windows.Threading.Dispatcher in Windows Form in my answer to question "Parallel Programming using TPL on WinForms" since the previous answer to your question:
If you are sure to be in UI thread (eg. in an button.Click handler), Dispatcher.CurrentDispatcher gives you the UI thread dispatcher that you can use to dispatch from background thread to UI thread as usual.
is either misleading or confusing or lacks the concrete usage context:
button.Clickhandler does not assure to be on UI thread;- if you are not on UI thread, it is still possible to use the disparcher of UI thread of WinForms form
One can get dispatcher of WinForm UI thread:
Dispatcher dispatcherUI = Dispatcher.CurrentDispatcher;
in either button click event handler or anywhere else (in form constructor)
And then use it to execute on UI from other threads, see more details on example below in my answer:
private void button1_Click(object sender, EventArgs e)
{
Dispatcher dispUI = Dispatcher.CurrentDispatcher;
for (int i = 2; i < 20; i++)
{
int j = i;
var t = Task.Factory.StartNew
(() =>
{
var result = SumRootN(j);
dispUI.BeginInvoke
(new Action
(() => richTextBox1.Text += "root " + j.ToString()
+ " " + result.ToString() + Environment.NewLine
)
, null
);
}
);
}
Solution 4:[4]
Use background worker thread as it's UI message pump aware, This MSDN Article though mostly about WPF does state that the BWT is UI aware even for windows forms.
Solution 5:[5]
I had similar problem using Oracle dependency class which runs on its own thread within Winforms,
When OnChange event fired from Oracle Dependency, I wanted to show the changes in DataGridView by simply setting DataSource to eventargs.Details (which is essentially a DataTable), and it throws: System.InvalidOperationException was unhandled by user code Message=Cross-thread operation not valid: Control 'dataGridView1' accessed from a thread other than the thread it was created on.
StackOverflow user Brian Peiris ([email protected]) ,collegue of mine showed me this way around:
void dep_OnChange(object sender, OracleNotificationEventArgs arg)
{
Console.WriteLine("Notification received");
int infoSum = int.Parse(arg.Details.Compute("Sum(Info)", "Info is not null").ToString());
InfoSum x = (InfoSum)infoSum;
foreach (DataRow dr in arg.Details.Rows)
{
Console.WriteLine(string.Format("Operation(InfoSum)= {0}", Enum.GetName(typeof(InfoSum), x)));
Console.WriteLine(string.Format("ontable={0} Rowid={1},info={2}", dr.Field<string>("ResourceName"), dr.Field<string>("rowid"), dr.Field<Int32>("info")));
}
// Following will throw cross-thread
// dataGridView1.DataSource = arg.Details;
// instead of line above use the following
dataGridView1.BeginInvoke((Action)(()=>dataGridView1.DataSource = arg.Details));
IsNotified = true;
}
}
Solution 6:[6]
Take a look at backgrounder and see if it fits your needs.
Solution 7:[7]
Sometimes a Timer component is useful and easy to setup in WinForms, just set its interval and then enable it, then make sure the first thing you do in its Tick event handler is to disable itself.
I think Timer runs the code in its own thread, so you may still need to do a BeginInvoke (called upon the WinForm object [this]) to run your Action.
private WebBrowserDocumentCompletedEventHandler handler; //need to make it a class field for the handler below (anonymous delegates seem to capture state at point of definition, so they can't capture their own reference)
private string imageFilename;
private bool exit;
public void CaptureScreenshot(Uri address = null, string imageFilename = null, int msecDelay = 0, bool exit = false)
{
handler = (s, e) =>
{
webBrowser.DocumentCompleted -= handler; //must do first
this.imageFilename = imageFilename;
this.exit = exit;
timerScreenshot.Interval = (msecDelay > 0)? msecDelay : 1;
timerScreenshot.Enabled = true;
};
webBrowser.DocumentCompleted += handler;
Go(address); //if address == null, will use URL from UI
}
private void timerScreenshot_Tick(object sender, EventArgs e)
{
timerScreenshot.Enabled = false; //must do first
BeginInvoke((Action)(() => //Invoke at UI thread
{ //run in UI thread
BringToFront();
Bitmap bitmap = webBrowser.GetScreenshot();
if (imageFilename == null)
imageFilename = bitmap.ShowSaveFileDialog();
if (imageFilename != null)
{
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(imageFilename))); //create any parent directories needed
bitmap.Save(imageFilename);
}
bitmap.Dispose(); //release bitmap resources
if (exit)
Close(); //this should close the app, since this is the main form
}), null);
}
you can see the above in action at WebCapture tool (http://gallery.clipflair.net/WebCapture, source code at: http://ClipFlair.codeplex.com, see Tools/WebCapture folder) that grabs screenshots from websites. BTW, if you want to call the executable from command-line make sure you go to Properties of the project and at Security tab turn-off ClickOnce security (else it can't access command-line)
Solution 8:[8]
I use ViewModels with databinding directly to properies in the ViewModel itself. In some cases, the properties are updated on a different thread. To avoid crashing I make use of the Dispatcher. When the ViewModel is instantiated it captures the current dispatcher and can then use it later as needed.
The one assumption I make is that the ViewModel itself is created on the main thread, and this is easily guaranteed as my ViewModels are always created in the constructor of the associated view (Form/Control), which always runs on the UI thread by design.
I created helper methods to set a property value. This helper calls RaisePropertyChanged. I made a "thread safe" override that can be used to ensure the raised event is fired on the main thread. When done this way, the UI component that is bound to the property will update itself on the UI thread even though the propery was updated on a different thread.
So for me it looks something like this:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Dispatcher _dispatcher;
public ViewModelBase()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
protected void RaisePropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
RaisePropertyChanged(propertyName);
return true;
}
protected bool SetFieldOnMainThread<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
RunOnUiThread(() => RaisePropertyChanged(propertyName));
return true;
}
protected void RunOnUiThread(Action action)
{
if (action != null)
{
_dispatcher.Invoke(action);
}
}
}
// Used like this:
public class TestViewModel : ViewModelBase
{
private string _name;
public string Name {
get => _name;
set => SetFieldOnMainThread(ref _name, value);
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | OJ. |
| Solution 3 | Community |
| Solution 4 | Preet Sangha |
| Solution 5 | TonyP |
| Solution 6 | rosenfield |
| Solution 7 | George Birbilis |
| Solution 8 | Glaucus |
