WinForms App w/BackgroundWorker & Class Object Events - c#

I'm working on a WinForms application that does a bunch of File Processing. This processing is done in multiple class objects that have "events" to publish different types of messages. One type is a "status" of what is being worked on and Two is an "output" that documents issues or change that were made.
I added a BackgroundWorker process and have that working fine but the only notifications I see is a ProgressPercentage. If I subscribe & raise any of the class events I get a "Cross-thread operation .. " which is understandable. What is the best way to implement this so that these components can be used by a winforms app and a non interactive process as well?
Here is what my DoWork looks like now, but the events cause the "Cross-thread":
var search = SearchToolFactory.Get(Convert.ToInt32(checkedTypeButton.Tag));
search.RaiseUpdateSearchEvent += new EventHandler<UpdateEventArgs>(search_RaiseUpdateSearchEvent);
search.RaiseUpdateOutputEvent += new EventHandler<UpdateEventArgs>(search_RaiseUpdateOutputEvent);
search.Process(Convert.ToInt32(checkedScopeButton.Tag), txtInput.Text, txtPattern.Text);
And here is the UpdateEventArgs, just a smilple class to pass a message:
public class UpdateEventArgs: EventArgs
{
public UpdateEventArgs(string s)
{
update = s;
}
private string update;
public string Update
{
get { return update; }
set { update = value; }
}
}
So based on PMF's comment I got the following to work:
void search_RaiseUpdateOutputEvent(object sender, UpdateEventArgs e)
{
if (InvokeRequired)
{
this.Invoke( (MethodInvoker)delegate{ txtOutput.Text += e.Update; });
}
}
void search_RaiseUpdateSearchEvent(object sender, UpdateEventArgs e)
{
if (InvokeRequired)
{
this.Invoke((MethodInvoker)delegate { txtSearching.Text = e.Update; });
}
}
But also see and understand the concern Hans Passant stated about structural issues and that my architecture is flawed and still needs additional work.
Thanks
dbl

BackgroundWorker.ReportProgress() has an overload with two arguments. The second can be anything you want. Like a delegate:
public event EventHandler Foo;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
//...
backgroundWorker1.ReportProgress(0, new Action(() => {
var handler = Foo;
if (handler != null) handler(this, EventArgs.Empty);
}));
//...
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) {
if (e.UserState != null) ((Action)e.UserState)();
else this.progressBar1.Value = e.ProgressPercentage; // optional
}

You need to Invoke() any user-interface event from your background-worker thread. Or you invoke the actual update in the form code (the later solution is probably the nicer way because it keeps the UI logic to the UI part of the application).
That would look similar to the following (for an event handler listening to status changes):
public void OnStatusUpdated(Status newStatus)
{
if (InvokeRequired)
{
Invoke(delegate
{
statusControl.Text = newStatus.ToString(); // Or something like it.
});
}
}

Related

Accessing controls in Form from a derived BackgroundWorker class results in cross-thread error

I'm working on writing a class which is derived from the System.ComponentModel.BackgroundWorker class. The reason I am doing so in my project is that I need a lot of information to be returned in different types of status update events, depending on which event is raised. When attempting to update any of the controls the main form from any of my update events, I am getting the following error:
System.InvalidOperationException: 'Cross-thread operation not valid:
Control '' accessed from a thread other than the thread it was created
on.'
The first control that I am attempting to update is a ToolStripStatusLabel, which does not have an .Invoke() method. I have created minimally verifiable example below. To recreate the error, simply create a new Windows Forms App (.NET Framework) project targeted to .NET 4.8 and copy paste the following code into the Form1.cs file:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private StatusStrip statusStrip1;
private ToolStripStatusLabel toolStripStatusLabel1;
private ToolStripProgressBar toolStripProgressBar1;
private Button button1;
private MyBGW myBGW;
public Form1()
{
InitializeComponent();
this.statusStrip1 = new StatusStrip();
this.toolStripStatusLabel1 = new ToolStripStatusLabel() { Text = "Starting Text" };
this.toolStripProgressBar1 = new ToolStripProgressBar();
this.button1 = new Button();
this.myBGW = new MyBGW();
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {this.toolStripStatusLabel1, this.toolStripProgressBar1});
this.Controls.Add(this.statusStrip1);
this.Controls.Add(this.button1);
this.button1.Click += Button1_Click;
this.myBGW.OnMyBGW_StatusChanged += MyBGW_OnMyBGW_StatusChanged;
}
private void Button1_Click(object sender, EventArgs e) { myBGW.RunWorkerAsync(); }
private void MyBGW_OnMyBGW_StatusChanged(object sender, MyBGW.MyBGW_StatusChanged_EventArgs e)
{
// The following two lines will throw the cross-threading exception
this.toolStripStatusLabel1.Text = e.StatusText;
if (e.PBarStyle != MyBGW.pBarStyles.NoChange) { this.toolStripProgressBar1.Style = (ProgressBarStyle)e.PBarStyle; }
}
}
public class MyBGW : BackgroundWorker
{
public enum pBarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
public delegate void MyBGW_StatusChanged_EventHandler(object sender, MyBGW_StatusChanged_EventArgs e);
public event MyBGW_StatusChanged_EventHandler OnMyBGW_StatusChanged;
public class MyBGW_StatusChanged_EventArgs : EventArgs
{
public string StatusText;
public pBarStyles PBarStyle;
public MyBGW_StatusChanged_EventArgs(string statusText, pBarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
public new void RunWorkerAsync() { base.RunWorkerAsync(); }
private void myBGW_DoWork(object sender, DoWorkEventArgs e)
{
OnMyBGW_StatusChanged(this, new MyBGW_StatusChanged_EventArgs(DateTime.Now.ToString(), pBarStyles.Marquee));
System.Threading.Thread.Sleep(10000);
OnMyBGW_StatusChanged(this, new MyBGW_StatusChanged_EventArgs("Done", pBarStyles.Continuous));
}
public MyBGW() { base.DoWork += new DoWorkEventHandler(this.myBGW_DoWork); }
}
}
My best guess is that I am raising or consuming the event incorrectly which is causing the code to still be run on the worker thread instead of the main/UI thread, but I'm coming up short in my research on what I'm missing.
EDIT: this question is not related to Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on as it is not directly relying on a BackgroundWorker but is rather attempting to add additional events to a derived class, of which the addition of those events are causing the Cross-Thread exception. Also, the answer does not apply as the control attempting to be updated does not have the .Invoke method as the solution to that question stated.
The problem for this question is in relation to how the event was being raised, which was incorrectly, causing the consumption of that event to be on the wrong thread and raising the cross-thread exception.
The BackgroundWorker.DoWork event handler is supposed to do background work, and it's not intended for interacting with the UI. This handler is invoked on a ThreadPool thread, and interacting with UI components from any thread other than the UI thread is not allowed. The BackgroundWorker class offers two events that are raised on the UI thread¹, the ProgressChanged and the RunWorkerCompleted. You could take advantage of this, by invoking your StatusChanged event on the ProgressChanged event handler (or overriding the OnProgressChanged method), and passing your StatusChangedEventArgs as an argument of the ReportProgress method:
public class MyBGW : BackgroundWorker
{
public enum BarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
public delegate void StatusChangedEventHandler(object sender,
StatusChangedEventArgs e);
public event StatusChangedEventHandler StatusChanged;
public MyBGW() { this.WorkerReportsProgress = true; }
public class StatusChangedEventArgs : EventArgs
{
public string StatusText;
public BarStyles PBarStyle;
public StatusChangedEventArgs(string statusText, BarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
protected override void OnDoWork(DoWorkEventArgs e)
{
this.ReportProgress(-1,
new StatusChangedEventArgs(DateTime.Now.ToString(), BarStyles.Marquee));
base.OnDoWork(e);
this.ReportProgress(-1,
new StatusChangedEventArgs("Done", BarStyles.Continuous));
}
protected override void OnProgressChanged(ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == -1 && e.UserState is StatusChangedEventArgs args)
StatusChanged?.Invoke(this, args);
else
base.OnProgressChanged(e);
}
}
¹ To be precise, the ProgressChanged and RunWorkerCompleted events are raised on the SynchronizationContext.Current which is captured when the BackgroundWorker.RunWorkerAsync is invoked.
Because toolStripStatusLabel1 And toolStripProgressBar1 runs inside a thread other than the main thread, it needs to be Invoke. And since ToolStripStatusLabel And ToolStripProgressBar itself does not have an Invoke method, we use its parent Invoke method.
change MyBGW_OnMyBGW_StatusChanged to :
private void MyBGW_OnMyBGW_StatusChanged(object sender, MyBGW.MyBGW_StatusChanged_EventArgs e)
{
InvokeIfRequired(this, ()=>
{
this.toolStripStatusLabel1.Text = e.StatusText;
});
if (e.PBarStyle != MyBGW.pBarStyles.NoChange)
{
InvokeIfRequired(this, () =>
{
this.toolStripProgressBar1.Style = (ProgressBarStyle)e.PBarStyle;
});
}
}
add InvokeIfRequired method
public void InvokeIfRequired(Control control, MethodInvoker action)
{
if (control.InvokeRequired)
control.Invoke(action);
else
action();
}
As mjwills has stated in the comments of the question, I was not raising the event properly, which was causing the event to be consumed on the same worker thread. After looking at the link for the .NET source code of the BackgroundWorker class, I can see that there is a bit of code, AsyncOperation.Post() that has the method protected virtual void OnStatusChangedin the code below raised in the main thread rather than the worker thread.
public class MyBGW : BackgroundWorker
{
public enum pBarStyles { Block = 0, Continuous = 1, Marquee = 2, NoChange = -1 }
private static readonly object statusChangedKey = new object();
private AsyncOperation asyncOperation = null;
public MyBGW() { base.DoWork += new DoWorkEventHandler(this.myBGW_DoWork); }
public delegate void StatusChanged_EventHandler(object sender, StatusChanged_EventArgs e);
public event StatusChanged_EventHandler StatusChanged
{
add { this.Events.AddHandler(statusChangedKey, value); }
remove { this.Events.RemoveHandler(statusChangedKey, value); }
}
protected virtual void OnStatusChanged(StatusChanged_EventArgs e) { ((StatusChanged_EventHandler)Events[statusChangedKey])?.Invoke(this, e); }
private void StatusReporter(object arg) { OnStatusChanged((StatusChanged_EventArgs)arg); }
public void UpdateStatus(StatusChanged_EventArgs e) { asyncOperation.Post(new System.Threading.SendOrPostCallback(StatusReporter), e); }
public class StatusChanged_EventArgs : EventArgs
{
public string StatusText;
public pBarStyles PBarStyle;
public StatusChanged_EventArgs(string statusText, pBarStyles pBarStyle)
{
this.StatusText = statusText; this.PBarStyle = pBarStyle;
}
}
public new void RunWorkerAsync() { asyncOperation = AsyncOperationManager.CreateOperation(null); base.RunWorkerAsync(); }
private void myBGW_DoWork(object sender, DoWorkEventArgs e)
{
UpdateStatus(new StatusChanged_EventArgs(DateTime.Now.ToString(), pBarStyles.Marquee));
System.Threading.Thread.Sleep(3000);
UpdateStatus(new StatusChanged_EventArgs("Done", pBarStyles.Continuous));
}
}
I don't fully understand the how and why, but it works. Hopefully someone can comment below with a better explanation.

C# backgroundWorker cancellation and invoke

I have 2 questions about backgroundWorker: one is cancellation and another is invoking.
My code briefly looks like this:
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
}
for (int i = 0; i < 10; i++) {
System.Threading.Thread.Sleep(50);
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
EditProcess.Text(String.Format("Downloaded: {0}\r\n"));
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}
First, while DnldBgWorker is running, I clicked StopBtn to stop the DnldBgWorker and the asynchronous work would not stop. How should I stop DnldBgWorker?
Second, EditProcess.Text(String.Format("Downloaded: {0}\r\n")); would give me an error that cross-thread operation is not valid. I know that I should make a delegate to do this, but I don't know exactly how.
++) My code looks like it's doing very simple works in very complicated way, but I put really essential elements in this code so please understand
Let's address the issue before we get into the code
For some reason, you have a completely redundant loop waiting for cancel after your actual download is done. Hence BtnStop does not work for you
When you call EditProcess.Text from Dnld which is invoked in the BackgroundWorker context, you are accessing a GUI element from a thread which does not "own" it. You can read in detail about cross-thread operation here. In your case, you should do it via your ReportProgress call.
Now you can see how I have
Removed the redundant loop from GoDownload while moving the if (DnldBgWorker.CancellationPending) check to the download loop. This should make the StopBtn work now.
Added the ProgressChanged event handler to do the GUI change in the ExecuteBtn_Click. This is triggered by DnldBgWorker.ReportProgress call in the download loop of GoDownload method. Here we pass the custom formatted string as UserState
Also make sure that you have the enabled the ReportsProgress and SupportsCancellation properties like below, perhaps in your designer property box or in code lile DnldBgWorker.WorkerReportsProgress = true; DnldBgWorker.WorkerSupportsCancellation = true;
Hope everything else is clear with the code below.
public partial class App : Form {
//Some codes omitted
public EditProcess Process = new EditProcess(ProcessTextBox);
private void ExecuteBtn_Click (object sender, EventArgs e) {
//DnldBgWorker is a backgroundWorker.
Download Dnld = new Download(dir, Process);
DnldBgWorker.DoWork += (obj, e) => GoDownload(Dnld, urllist, e);
DnldBgWorker.RunWorkerAsync();
DnldBgWorker.RunWorkerCompleted += (obj, e) => FinishExecution();
DnldBgWorker.ProgressChanged += (s, e) => EditProcess.Text((string)e.UserState);;
}
private void GoDownload(Download Dnld, string[] urllist, EventArgs e) {
foreach(string url in urllist) {
Dnld.Dnld(url);
DnldBgWorker.ReportProgress(0, String.Format($"Downloaded: {url}\r\n"));
if (DnldBgWorker.CancellationPending) {
e.Cancel = true;
return;
}
}
}
private void StopBtn_Click(object sender, EventArgs e) {
DnldBgWorker.CancelAsync();
}
}
public class Download {
// Some codes omitted
public WebClient client = new WebClient();
public EditProcess Process;
public Download(string dir, EditProcess Process) {
this.dir = dir;
this.Process = Process;
}
public void Dnld() {
client.DownloadFile(url, dir);
}
}
public class EditProcess {
public TextBox Box;
public EditProcess(TextBox Box) {
this.Box = Box;
}
public void Text(string textToAdd) {
Box.Text += textToAdd;
}
}
There are 2 issues here:
Regarding cancellation - you need to check for cancellation status in the loop that does downloading (thus downloading only part of requested files), not in the later loop which I don't really understand.
As an additional side note you can avoid using BackgroundWorker by using WebClient.DownloadFileAsync and WebClient.CancelAsync combo.
As of reporting progress - make you BackgroundWorker report progress back to the UI thread via ReportProgress and update UI from there.
As for how to cancel a thread. Here is a basic example, for a console application, that I hope you can fit into your more complex code.
void Main()
{
var tokenSource = new CancellationTokenSource();
System.Threading.Tasks.Task.Run(() => BackgroundThread(tokenSource.Token));
Thread.Sleep(5000);
tokenSource.Cancel();
}
private void BackgroundThread(CancellationToken token)
{
while (token.IsCancellationRequested == false) {
Console.Write(".");
Thread.Sleep(1000);
}
Console.WriteLine("\nCancellation Requested Thread Exiting...");
}
The results would be the following.
.....
Cancellation Requested Thread Exiting...
Secondly, as far as how to invoke from your thread to interact with the user interface, hopefully this blog will help you. Updating Windows Form UI elements from another thread
Please let me know if you found this helpful.
To support cancellation you need to set the property
DnldBgWorker.WorkerSupportsCancellation = true;
It is not clear if you set it somewhere else, but you need it to cancel the background worker as you can read on MSDN
Set the WorkerSupportsCancellation property to true if you want the
BackgroundWorker to support cancellation. When this property is true,
you can call the CancelAsync method to interrupt a background
operation.
Also I would change the GoDownload method to
private void GoDownload(Download Dnld, string[] urllist, EventArgs e)
{
foreach(string url in urllist)
{
Dnld.Dnld(url);
// this is just to give more time to test the cancellation
System.Threading.Thread.Sleep(500);
// Check the cancellation after each download
if (DnldBgWorker.CancellationPending)
{
e.Cancel = true;
return;
}
}
}
For the second problem you need to call that method when your code is running on the UI thread and not in the background thread. You could easily achieve this moving the textbox update in the event handler for the ProgressChanged event. To set up the event handler you need another property set to true
DnldBgWorker.WorkerReportsProgress = true;
And set the event handler for the ProgressChanged event
DnldBgWorker.ProgressChanged += DnldBgWorker_ProgressChanged;
private void DnldBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
EditProcess.Text(String.Format("Downloaded: {0}\r\n", e.ProgressPercentage));
}
and raise this event in the GoDownload with
DnldBgWorker.ReportProgress(i);

Avoid requiring Dispatcher.BeginInvoke

I'm writing a library to control an application over TCP. The connections are handled asynchronously so I've added an event to the communication class to indicate a message has been received.
public event EventHandler<MessageRecievedEventArgs> MessageRecieved;
But when I raise the event the event handler in the main class executes the event handler on the TCP thread not the main thread.
How do I avoid requiring the user to update the GUI by invoking?
private void MessageRecieved(object sender, MessageRecievedEventArgs e)
{
Dispatcher.BeginInvoke((Action)(()=> { textBox1.Text = e.Message; }));
}
Using Hans Passant's comment above, I just modified my code as follows:
private SynchronizationContext MainUIThread; //as a class field
In the constructor:
public MyClass()
{
MainUIThread = SynchronizationContext.Current;
}
Modification to the event structure:
public event EventHandler<MessageRecievedEventArgs> MessageRecieved;
protected virtual void OnMessageReceived(object sender, MessageRecievedEventArgs args)
{
var handle = MessageRecieved;
if (handle == null)
return;
if(MainUIThread != null)
{
MainUIThread.Post(d => handle(sender, args), this);
}
else
{
handle(sender, args);
}
}

MethodInvoker for a button

Now i use a method that update all the time when connection event is raised, but now for connection i use a different thread an i have problem to call that event from Icommand thread i get an error System.StackOwverflowException, i read on internet bout this problem and this problem i think cam solved with Invoker but i do not understand the concept of invoker how to use it.
My code:
public class NewMeasurementCommand : ICommand
{
private MeasurementViewModel measurementViewModel = null;
private BaseViewModel baseViewModel;
private GpfGateway gpfGateway;
public NewMeasurementCommand(MeasurementViewModel viewModel)
{
measurementViewModel = viewModel;
GpfGateway.GetInstance().SystemStatus += updateCanExecuteChanged;
}
/// <summary>Notifies command to update CanExecute property.</summary>
private void updateCanExecuteChanged(object sender, EventArgs e)
{
updateCanExecuteChanged(sender, new EventArgs());
}
bool ICommand.CanExecute(object parameter)
{
return GpfGateway.GetInstance().IsConnected;
}
public event EventHandler CanExecuteChanged;
void ICommand.Execute(object parameter)
{
NewMeasurementViewModel newMeasurementViewModel = new NewMeasurementViewModel();
measurementViewModel.MeasurementModel.MeasurementStep = new MeasurementInfoStep();
measurementViewModel.MeasurementModel.MeasurementStep.NewMeasurementVM = newMeasurementViewModel;
GpfGateway.GetInstance().ControllerValuesArrived += measurementViewModel.MeasurementModel.MeasurementStep.RemoteControllerArrived;
newMeasurementViewModel.MeasurementModel = measurementViewModel.MeasurementModel;
newMeasurementViewModel.MeasurementModel.CurrentMeasurement = new Measurement();
measurementViewModel.MeasurementModel.MeasurementStep.CurrentMeasurement = newMeasurementViewModel.MeasurementModel.CurrentMeasurement;
newMeasurementViewModel.NavigationResolver = measurementViewModel.NavigationResolver;
measurementViewModel.MeasurementModel.CurrentMeasurement = new Measurement();
measurementViewModel.MeasurementModel.MeasurementStep.CurrentMeasurement = measurementViewModel.MeasurementModel.CurrentMeasurement;
measurementViewModel.MeasurementModel.CurrentMeasurement.Tester = Environment.UserName;
measurementViewModel.NavigationResolver.GoToMeasurementInfoStep(newMeasurementViewModel);
}
private void updateCanExecuteChanged(object sender, NotifyCollectionChangedEventArgs e)
{
CanExecuteChanged(this, new EventArgs());
}
}
}
What I try to make:
public delegate void InvokeDelegate();
private void EnabledChanged(object sender, EventArgs e)
{
this.BeginInvoke((MethodInvoker)delegate
{
EnabledChanged();
});
if(sender == gpfGateway.IsConnected);
}
If anyone have some advice it will be very helpful.
You're getting a stack overflow because your EnabledChanged function calls itself. So its just going round and round recalling itself until it goes boom.
If you look at Microsofts invoke
You'll see they dont call the function from itself, but from somewhere else.
What you can do is something like this
private void UpdateStatus(String message)
{
if (this.InvokeRequired)
this.Invoke((MethodInvoker)delegate
{
UpdateStatus(message);
});
else
label1.Text = message;
}
While this is setting text on a label, its still a reasonable example. Any GUI element cannot be played with from another thread, so, you can use a function such as this from anywhere, if it needs to it then invokes the command. You can have it call itself, in this instance, because after invoking it wont need to invoke itself again.

C#: Triggering an Event when an object is added to a Queue

I need to be able to trigger a event whenever an object is added to a Queue<Delegate>.
I created a new class that extends Queue:
public delegate void ChangedEventHandler(object sender, EventArgs e);
public class QueueWithChange<Delegate> : Queue<Delegate>
{
public event ChangedEventHandler Changed;
protected virtual void OnChanged(EventArgs e) {
if (Changed != null)
{
Changed(this, e);
}
}
}
And then attached the event from another class, like such:
QueueWithChange<TimerDelegate> eventQueue = new QueueWithChange<TimerDelegate>();
//
eventQueue.Changed += new ChangedEventHandler(delegate(object s, EventArgs ex) {
//This event is not being triggered, so this code is unreachable atm...and that is my problem
if (eventQueue.Count > 0)
{
eventQueue.Dequeue().Invoke(new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(5) });
actionTimer.Stop();
}
});
But whenever I enqueue an object (eventQueue.Enqueue(something)), the attached event is not being fired.
What am I missing here?
If you mean the non-generic Queue class, then you can just override Enqueue:
public override void Enqueue(object obj)
{
base.Enqueue(obj);
OnChanged(EventArgs.Empty);
}
However, if you mean the generic Queue<T> class, then note that there is no suitable virtual method to override. You might do better to encapsulate the queue with your own class:
(** important edit: removed base-class!!! **)
class Foo<T>
{
private readonly Queue<T> queue = new Queue<T>();
public event EventHandler Changed;
protected virtual void OnChanged()
{
if (Changed != null) Changed(this, EventArgs.Empty);
}
public virtual void Enqueue(T item)
{
queue.Enqueue(item);
OnChanged();
}
public int Count { get { return queue.Count; } }
public virtual T Dequeue()
{
T item = queue.Dequeue();
OnChanged();
return item;
}
}
However, looking at your code, it seems possible that you are using multiple threads here. If that is the case, consider a threaded queue instead.
I just did write up on what I call a TriggeredQueue. It's inspired the answer by Marc Gravell.
You can find my post here: http://joesauve.com/triggeredqueuet
And the Gist here: http://gist.github.com/jsauve/b2e8496172fdabd370c4
It has four events:
WillEnqueue
WillDequeue
DidEnqueue
DidDequeue
You can hook into any of these like so:
YourQueue.WillEnqueue += (sender, e) => {
// kick off some process
};
YourQueue.DidEnqueue += (sender, e) => {
// kick off some process
// e.Item provides access to the enqueued item, if you like
};
YourQueue.WillDequeue += (sender, e) => {
// kick off some process
};
YourQueue.DidDequeue += (sender, e) => {
// kick off some process
// e.Item provides access to the dequeued item, if you like
};
One neat trick is that you can use the DidDequeue method to kick off some process to ensure that the queue is full by making a web request or loading some data from a filesystem, etc. I use this class in Xamarin mobile apps to ensure that data and images are pre-cached in order to provide a smooth user experience, instead of loading images AFTER they scroll onto the screen (like you might see in Facebook and countless other apps).
try
public new void Enqueue(Delegate d)
{
base.Enqueue(d);
OnChanged(EventArgs.Empty);
}
You have to override Enqueue, to call OnChanged.

Categories

Resources