WPF Dispatcher.BeginInvoke and UI/Background Threads - wpf

I think I need some clarifications regarding WPFs Dispatcher.Invoke and Dispatcher.BeginInvoke usage.
Suppose I have some long running 'work' code like such that is invoked on the press of a button in a simple WPF application:
longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
{
Console.WriteLine("Starting Work Action");
int i = int.MaxValue;
while (i > 0)
i--;
Console.WriteLine("Ending Work Action");
longWorkTextBox.Text = "Work Complete";
};
longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);
This code is locking up my user interface while the workAction is being performed. This is because Dispatcher invokes always run on the UI thread, right?
Assuming this, what is the best practice for configuring my dispatcher to execute the workAction in a separate thread from my UI? I know I can add a BackgroundWorker to my workAction to prevent my UI from locking as such:
longWorkTextBox.Text = "Ready For Work!";
Action workAction = delegate
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Console.WriteLine("Starting Slow Work");
int i = int.MaxValue;
while (i > 0)
i--;
Console.WriteLine("Ending Work Action");
};
worker.RunWorkerCompleted += delegate
{
longWorkTextBox.Text = "Work Complete";
};
worker.RunWorkerAsync();
};
longWorkTextBox.Dispatcher.BeginInvoke(DispatcherPriority.Background, workAction);
Is there any more elegant ways of doing this besides using the BackgroundWorker? I've always heard that the BackgroundWorker is quirky, so I am curious to know of some alternatives.

I honestly think the BackgroundWorker is the most elegant solution for this. I cannot think of a simpler way to do it.

Me too don't like BackgroundWorker.
A simple alternative can be something like:
using System;
using System.Threading;
using System.Windows;
namespace Sample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
longWorkTextBox.Text = "Ready For Work!";
}
private void startButton_Click(object sender, RoutedEventArgs e)
{
new Thread(Work).Start();
}
void Work()
{
longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Working..."; }));
Console.WriteLine("Starting Work Action");
int i = int.MaxValue;
while (i > 0)
i--;
Console.WriteLine("Ending Work Action");
longWorkTextBox.Dispatcher.BeginInvoke((Action)(() => { longWorkTextBox.Text = "Work Complete"; }));
}
}
}
Easy, not?

Charlie's answer is what you are looking for, really.
However, if it's possible you might look at whether or not you can parcel up your work so that the individual units of work are small and don't affect the UI as much. This would allow you to just use the Dispatcher directly. There is a good example of this on the WPF Threading page: https://msdn.microsoft.com/en-us/library/ms741870%28v=vs.100%29.aspx

As its name indicates it will execute in the Background so you don't need to instantiate it with the Dispatcher. Plus if you want this code to run into a WP7 the BeginInvoke does not get the background parameter.
My recommendation is to create the BackgroundWorker as:
BackgroundWorker worker = new BackgroundWorker;
And then create the handlers for the events:
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork +=new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted +=new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.ProgressChanged +=new ProgressChangedEventHandler(worker_ProgressChanged);
And finally you call:
bkwkPlayingLoop.RunWorkerAsync();
It is a big temptation to use the Dispatcher from inside the DoWork but instead call worker.ReportProgress() and handle the UI from there. You will otherwise face some inconsistencies with the firing of termination events.

Tasks are easier to use than Background workers, do more things, have fewer issues and were pretty much created so Background Workers didn't need to be used anymore...

Related

Add items to listbox By thread in WPF using C#

I am using the code below, however it is causing my application to hang and I am unsure as to why. Would anyone be able to help me out here?
void put_items() {
listb.Dispatcher.BeginInvoke(new Action(() =>
{
for (int i = 0; i < 9000000; i++)
{
listb.Items.Add(i.ToString());
}
}));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Thread mythread = new Thread(put_items);
mythread.Start();
}
If you want to update any UI controls this has to be done in the UI thread. By using the Dispatcher you force your application to execute the code within BeginInvoke-block to be executed on the ui thread.
Depending on how time consuming the work for one item in the for loop is, you could process a bunch of items (say 10 or 100) and then update the ui by using the dispatcher. Notice that each call of Dispatcher.BeginInvoke needs some time (maybe 500 ms).
Another way would be using an event aggregator see here. Then your class containing the button click method would register to the event aggregator and in the thread you would just need the instance of the aggregator and call ea.Publish(new YourCustomEvent(yourItemToUpdateUI)).
This approach is really nice if your application is going to be complex.
thank you but I think that code not work in netframwork 3.5 I think that I resolve By this code
public void put_items()
{
for (int i = 0; i < 999999999; i++)
{
this.Dispatcher.Invoke(new Action (() =>
{
listb.Items.Add(i.ToString());
}));
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
new Thread(put_items).Start();
}

WPF NotifyIcon From Background Thread

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.

System.Windows.Controls.WebBrowser, System.Windows.Threading.Dispatcher, and a windows service

I'm trying to render some html content to a bitmap in a Windows Service.
I'm using System.Windows.Controls.WebBrowser to perform the render. The basic rendering setup works as a standalone process with a WPF window hosting the control, but as a service, at least I'm not getting the LoadCompleted events to fire.
I know that I at least need a Dispatcher or other message pump looping for this WPF control. Perhaps I'm doing it right and there are just additional tricks/incompatibilities necessary for the WebBrowser control. Here's what I've got:
I believe only one Dispatcher needs to be running and that it can run for the life of the service. I believe the Dispatcher.Run() is the actual loop itself and thus needs it's own thread which it can otherwise block. And that thread needs to be [STAThread] in this scenario. Therefore, in a relevant static constructor, I have the following:
var thread = new Thread(() =>
{
dispatcher = Dispatcher.CurrentDispatcher;
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
where dispatcher is a static field. Again, I think there can only be one but I'm not sure if I'm supposed to be able use Dispatcher.CurrentDispatcher() from anywhere instead and get the right reference.
The rendering operation is as follows. I create, navigate, and dispose of the WebBrowser on dispatcher's thread, but event handler assignments and mres.Wait I think may all happen on the render request-handling operation. I had gotten The calling thread cannot access this object because a different thread owns it but now with this setup I don't.
WebBrowser wb = null;
var mres = new ManualResetEventSlim();
try
{
dispatcher.Invoke(() => { wb = new WebBrowser(); });
wb.LoadCompleted += (s, e) =>
{
// Not firing
};
try
{
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, Encoding.Unicode))
{
sw.Write(html);
sw.Flush();
ms.Seek(0, SeekOrigin.Begin);
// GO!
dispatcher.Invoke(() =>
{
try
{
wb.NavigateToStream(ms);
Debug.Assert(Dispatcher.FromThread(Thread.CurrentThread) != null);
}
catch (Exception ex)
{
// log
}
});
if (!mres.Wait(15 * 1000)) throw new TimeoutException();
}
}
catch (Exception ex)
{
// log
}
}
finally
{
dispatcher.Invoke(() => { if (wb != null) wb.Dispose(); });
}
When I run this, I get my timeout exception every time since the LoadCompleted never fires. I've tried to verify that the dispatcher is running and pumping properly. Not sure how to do that, but I hooked a few of the dispatcher's events from the static constructor and I get some printouts from that, so I think it's working.
The code does get to a wb.NavigateToStream(ms); breakpoint.
Is this bad application of Dispatcher? Is the non-firing of wb.LoadCompleted due to something else?
Thanks!
Here's a modified version of your code which works as a console app. A few points:
You need a parent window for WPF WebBrowser. It may be a hidden window like below, but it has to be physically created (i.e. have a live HWND handle). Otherwise, WB never finishes loading the document (wb.Document.readyState == "interactive"), and LoadCompleted never gets fired. I was not aware of such behavior and it is different from the WinForms version of WebBrowser control. May I ask why you picked WPF for this kind of project?
You do need to add the wb.LoadCompleted event handler on the same thread the WB control was created (the dispatcher's thread here). Internally, WPF WebBrowser is just a wrapper around apartment-threaded WebBrowser ActiveX control, which exposes its events via IConnectionPointContainer interface. The rule is, all calls to an apartment-threaded COM object must be made on (or proxied to) the thread the object was originally created on, because that's what such kind of objects expect. In that sense, IConnectionPointContainer methods are no different to other methods of WB.
A minor one, StreamWriter automatically closes the stream it's initialized with (unless explicitly told to not do so in the constructor), so there is no need to for wrapping the stream with using.
The code is ready to compile and run (it requires some extra assembly references: PresentationFramework, WindowsBase, System.Windows, System.Windows.Forms, Microsoft.mshtml).
using System;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Controls;
using System.IO;
using System.Runtime.InteropServices;
using mshtml;
namespace ConsoleWpfApp
{
class Program
{
static Dispatcher dispatcher = null;
static ManualResetEventSlim dispatcherReady = new ManualResetEventSlim();
static void StartUIThread()
{
var thread = new Thread(() =>
{
Debug.Print("UI Thread: {0}", Thread.CurrentThread.ManagedThreadId);
try
{
dispatcher = Dispatcher.CurrentDispatcher;
dispatcherReady.Set();
Dispatcher.Run();
}
catch (Exception ex)
{
Debug.Print("UI Thread exception: {0}", ex.ToString());
}
Debug.Print("UI Thread exits");
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
static void DoWork()
{
Debug.Print("Worker Thread: {0}", Thread.CurrentThread.ManagedThreadId);
dispatcherReady.Wait(); // wait for the UI tread to initialize
var mres = new ManualResetEventSlim();
WebBrowser wb = null;
Window window = null;
try
{
var ms = new MemoryStream();
using (var sw = new StreamWriter(ms, Encoding.Unicode)) // StreamWriter automatically closes the steam
{
sw.Write("<b>Hello, World!</b>");
sw.Flush();
ms.Seek(0, SeekOrigin.Begin);
// GO!
dispatcher.Invoke(() => // could do InvokeAsync here as then we wait anyway
{
Debug.Print("Invoke Thread: {0}", Thread.CurrentThread.ManagedThreadId);
// create a hidden window with WB
window = new Window()
{
Width = 0,
Height = 0,
Visibility = System.Windows.Visibility.Hidden,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Content = wb = new WebBrowser();
window.Show();
// navigate
wb.LoadCompleted += (s, e) =>
{
Debug.Print("wb.LoadCompleted fired;");
mres.Set(); // singal to the Worker thread
};
wb.NavigateToStream(ms);
});
// wait for LoadCompleted
if (!mres.Wait(5 * 1000))
throw new TimeoutException();
dispatcher.Invoke(() =>
{
// Show the HTML
Console.WriteLine(((HTMLDocument)wb.Document).documentElement.outerHTML);
});
}
}
catch (Exception ex)
{
Debug.Print(ex.ToString());
}
finally
{
dispatcher.Invoke(() =>
{
if (window != null)
window.Close();
if (wb != null)
wb.Dispose();
});
}
}
static void Main(string[] args)
{
StartUIThread();
DoWork();
dispatcher.InvokeShutdown(); // shutdown UI thread
Console.WriteLine("Work done, hit enter to exit");
Console.ReadLine();
}
}
}
Maybe the Webbrowser Control needs Desktop Interaction for rendering the content:
My feeling say that using WPF controls and in particular particulary the Webbrowser-Control (=Wrapper around the IE ActiveX control) isn't the best idea.. There are other rendering engines that might be better suited for this task: Use chrome as browser in C#?

"The calling thread must be STA" workaround

I know there are a few answers on this topic on SO, but I can not get any of the solutions working for me. I am trying to open a new window, from an ICommand fired from within a datatemplate. Both of the following give the aforementioned error when the new window is instantiated (within "new MessageWindowP"):
Using TPL/FromCurrentSynchronizationContext Update: works
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(new Action<object>(CreateMessageWindow), user,CancellationToken.None, TaskCreationOptions.None,scheduler);
}
}
private void CreateMessageWindow(object o)
{
var user = (UserC)o;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
Using ThreadStart: Update: not recommended, see Jon's answer
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
var t = new ParameterizedThreadStart(CreateMessageWindow);
var thread = new Thread(t);
thread.SetApartmentState(ApartmentState.STA);
thread.Start(sender);
}
}
private void CreateMessageWindow(object o)
{
var user = (UserC)o;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
Thanks
EDIT. Based on the responses so far, I'd like to point out that I have also tried BeginInvoke on the current dispatcher, as well as executing the code in the original method (that's how the code started). See below:
BeginInvoke Update: not recommended see Jon's answer
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
Dispatcher.CurrentDispatcher.BeginInvoke(new Action<object>(CreateMessageWindow), sender);
}
}
private void CreateMessageWindow(object o)
{
var user = (UserC)o;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
In same thread Update: works if you are on UI thread already
public class ChatUserCommand : ICommand
{
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
var messageP = new MessageWindowP();
messageP.ViewModel.Participants.Add(user);
messageP.View.Show();
}
}
}
BeginInvoke, using reference to dispatcher of first/main window Update: works
public void Execute(object sender)
{
if (sender is UserC)
{
var user = (UserC)sender;
GeneralManager.MainDispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => this.CreateMessageWindow(user)));
}
}
where GeneralManager.MainDispatcher is a reference to the Dispatcher of the first window I create:
[somewhere far far away]
mainP = new MainP();
MainDispatcher = mainP.View.Dispatcher;
I'm at a loss.
The calling thread must not only be STA, but it must also have a message loop. There's only one thread in your application that already has a message loop, and that's your main thread. So you should use Dispatcher.BeginInvoke to open your window from your main thread.
E.g. if you have a reference to your main application window (MainWindow), you can do
MainWindow.BeginInvoke(
DispatcherPriority.Normal,
new Action(() => this.CreateMessageWindow(user)));
Update: Be careful: you cannot blindly call Dispatcher.CurrentDispatcher because it doesn't do what you think it does. The documentation says that CurrentDispatcher:
Gets the Dispatcher for the thread currently executing and creates a
new Dispatcher if one is not already associated with the thread.
That's why you must use the Dispatcher associated with an already-existing UI control (like your main window as in the example above).
With TPL you can use the StaTaskScheduler from the TPL Extras
It will run tasks on STA threads.
Only used it for COM. Never tried to run several UI threads.
You are trying to create a window from a background thread. You cannot do it due to a variety of reasons. Typically you need to create the window in the main application thread.
For your case, a simple idea would be just do it immediately (just call CreateMessageWindow inside Execute) instead of allocating a Task, because your command will definitely fire from the main (UI) thread. If you are unsure about the thread where your Execute runs, you can marshal it to the UI thread using Dispatcher.BeginInvoke().
There are really few cases when you would want your new window to run in a non-main thread. If this is really really needed in your case, you should add Dispatcher.Run(); after messageP.View.Show(); (using the second variant of the code). This starts the message loop in the new thread.
You shouldn't try to run window in TPL's thread, because these threads are typically threadpool threads, and therefore out of your control. For example, you cannot ensure they are STA (typically, they are MTA).
Edit:
from the error in your updated code, it's clear that the Execute runs in some non-UI thread. Try using Application.Current.Dispatcher instead of Dispatcher.CurrentDispatcher. (CurrentDispatcher means the dispatcher of the current thread, which may be wrong if the current thread is not the main one.)

Can I use Monitor.Enter/Exit (c# lock) with WPF without fear of reentrancy bugs?

If I use Monitor.Enter/Exit (through the c# lock syntax) in a WPF application, can the dispatcher cause re-entrance?
In the sample below, presuming OnTextChanged is called when the text in a textbox changes, could the call to _worker.RunWorkerAsync() be called incorrectly?
public class SomeClass
{
private object _locker = new object();
private bool _running = false;
private BackgroundWorker _worker;
public void SomeClass()
{
// initialize worker...
}
void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lock (_locker)
_running = false;
}
void _worker_DoWork(object sender, DoWorkEventArgs e)
{
// ... do something time consuming ...
}
private void OnTextChanged()
{
lock(_locker)
{
if (!_running)
{
_worker.RunWorkerAsync();
_running = true;
}
}
}
}
I believe it's possible, but I've not been able to reproduce this. Does WPF somehow prevent the dispatcher from invoking waiting tasks when waiting on monitor?
Not sure what you fear. Both OnTextChanged and RunWorkerCompleted run on the UI thread. It won't be re-entrant, you don't need the lock either. Either method can only start running when the UI thread is idle, pumping the message loop.
While not directly related to your question, you could run into register caching issues if you don't mark _running as volatile.
Actually this isn't strictly true, as you are not using a double-checked lock. I've left the information related to volatile there anyway, for your reference.

Resources