MethodInvoker for a button - c#

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.

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.

Passing events between threads run from separate class C#

I'm struggling to pass data between a thread started in a separate class from my main form. I believe (I could be wrong) that I should use an event. The problem I have is my subscribers are always null as I call the BluetoothScan class and start the thread before the event is subscribed to:
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
How do I subscribe to the event before starting the thread?
I have my Main Form:
using System;
using System.Windows.Forms;
//https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.invoke?view=net-5.0#System_Windows_Forms_Control_Invoke_System_Delegate_System_Object___
namespace YieldMonitor
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
}
private void BtnConnectBT_Click(object sender, EventArgs e)
{
//Start looking for the yield monitor device.
BluetoothScan bluetoothScan = new BluetoothScan(this);
bluetoothScan.BluetoothDeviceDiscovered += OnBluetoothDeviceDiscovered;
}
static void OnBluetoothDeviceDiscovered(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Message recieved from event");
}
}
}
My class that looks for bluetooth devices and if the right one is found should fire the event:
using InTheHand.Net.Sockets;
using System;
using System.Linq;
namespace YieldMonitor
{
class BluetoothScan
{
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
Run();
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Running BluetoothScan Class");
string myDeviceName;
ulong myDeviceAddress;
BluetoothClient btClient = new BluetoothClient();
BluetoothDeviceInfo[] btDevices = btClient.DiscoverDevices().ToArray();
foreach (BluetoothDeviceInfo d in btDevices)
{
System.Diagnostics.Debug.WriteLine(d.DeviceName);
System.Diagnostics.Debug.WriteLine(d.DeviceAddress);
//have we found the device we are looking for?
if (d.DeviceName == "DSD TECH HC-05")
{
myDeviceName = d.DeviceName;
myDeviceAddress = d.DeviceAddress;
//Send out found adapter to the next stage
OnBluetoothScanned(EventArgs.Empty);
break;
}
}
}
protected virtual void OnBluetoothScanned(EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Running OnBlueToothScanned");
EventHandler handler = BluetoothDeviceDiscovered;
if (handler != null)// we have a subscriber to our event
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Not empty");
handler(this, e);
}
else
{
System.Diagnostics.Debug.WriteLine("BluetoothScanned is Empty");
}
}
}
}
EDIT
I've found some nice solutions using Tasks where I need to update a label once a task is completed ie.
bool myDevicePaired = false;
var eventDevicePaired = new Progress<bool>(boDevicePaired => myDevicePaired = boDevicePaired);
await Task.Factory.StartNew(() => BluetoothPair.Run(myDeviceAddress, eventDevicePaired), TaskCreationOptions.LongRunning);
//Register the device is paired with the UI
if (myDevicePaired)
{
BtnConnectBT.Text = "Disconnect?";
}
Which is working well for Tasks that have an end that I am waiting for example waiting for a bluetooth device to connect.
But I'm beginning to pull my hair out with System.InvalidOperationException: 'Cross-thread operation not valid: Control 'tbInfo' accessed from a thread other than the thread it was created on.' error when trying to update a form text box.
Example:
in my MainForm Class:
I create what I've called an Event Reciever...
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!"); //writes data to debug fine
tbInfo.AppendText("Event!!!!"); //causing error
}
I create a task to read from the device...
private void ReadDataFromDevice(UInt64 myDeviceAddress)
{
BluetoothSocket bluetoothSocket = new BluetoothSocket(myDeviceAddress);
bluetoothSocket.BluetoothDataRecieved += BluetoothSocketEventReciever;
Task.Factory.StartNew(() => bluetoothSocket.Run(), TaskCreationOptions.LongRunning);
}
In my BluetoothSocket class I have an endless while loop which will be reading data from a socket (hopefully) At the moment its just creating an empty EventArgs to trigger the Event every second:
namespace YieldMonitor
{
class BluetoothSocket
{
ulong myDeviceAddress;
public event EventHandler BluetoothDataRecieved;
public BluetoothSocket (ulong deviceAddress)
{
myDeviceAddress = deviceAddress;
}
public void Run()
{
System.Diagnostics.Debug.WriteLine("Were in BluetoothSocket ... Address: " + myDeviceAddress);
while (true)
{
Thread.Sleep(1000);
Debug.WriteLine("In BluetoothSocket - Address = " + myDeviceAddress);
OnBluetoothDataRecieved(EventArgs.Empty);
}
}
protected virtual void OnBluetoothDataRecieved(EventArgs e)
{
EventHandler handler = BluetoothDataRecieved;
if (handler != null)
{
handler(this, e);
} else
{
//No subscribers
}
}
}
}
I'm sure I'm missing something simple here but how can I pass the data from the endless loop to the text box on the main form?
EDIT
Think I've just sorted it.
private void BluetoothSocketEventReciever(object sender, EventArgs e)
{
Debug.WriteLine("Event!!!");
tbInfo.Invoke((Action)delegate
{
tbInfo.AppendText("Event!!!");
});
//tbInfo.AppendText("Event!!!!");
}
Is this the correct way to do it?
You can Pass the event handler as a parameter on the constructor
public event EventHandler BluetoothDeviceDiscovered;
public BluetoothScan(MainForm mainForm, EventHandler bluetoothDeviceDiscovered)
{
System.Diagnostics.Debug.WriteLine("Starting BluetoothScan Class");
BluetoothDeviceDiscovered += bluetoothDeviceDiscovered
Run();
}
Personally, i'm not so fun of calling method on constructor. It can be source of bugs or performance issues
Constructor
In class-based object-oriented programming, a constructor
(abbreviation: ctor) is a special type of subroutine called to create
an object. It prepares the new object for use, often accepting
arguments that the constructor uses to set required member variables.
You can pass eventhandler as parameter and call Run later

ICommand exception, thread cannot access the object

I use this class to enable and disable a button when the network is
connected i rise an event in Gpfgateway class to notify the net is
connected and the button will be disable, when i start the app at the
begging work after i disconnect or connect the network throw an
exception. The event handler in the Gpfgateway is Thread.
Exception:
System.InvalidoperationException in windowsBase.dll
Additional info: The calling thread cannot access this because a
different thread owns it.
refer to this line of code:
CanExecuteChanged(this, new EventArgs())
Code:
public class NewAnalysisCommand : ICommand
{
private AnalysisViewModel analysisViewModel = null;
private Measurement measurement;
public NewAnalysisCommand(AnalysisViewModel viewAnalysis)
{
analysisViewModel = viewAnalysis;
GpfGateway.GetInstance().SystemStatus += updateCanExecuteChanged;
}
/// <summary>Notifies command to update CanExecute property.</summary>
private void updateCanExecuteChanged(object sender, EventArgs e)
{
CanExecuteChanged(this, new EventArgs());
}
bool ICommand.CanExecute(object parameter)
{
return GpfGateway.GetInstance().IsConnected;
}
public event EventHandler CanExecuteChanged;
void ICommand.Execute(object parameter)
{
NewAnalysisViewModel newAnalysisViewModel = new NewAnalysisViewModel();
newAnalysisViewModel.NavigationResolver = analysisViewModel.NavigationResolver;
// set CurrentPosition to -1 so that none is selected.
analysisViewModel.Measurements.MoveCurrentToPosition(-1);
analysisViewModel.Measurements.Refresh();
if(((List<MeasurementViewModel>)(analysisViewModel.Measurements.SourceCollection)).Count == 0)
{
CanExecuteChanged(this, new EventArgs());
}
analysisViewModel.NavigationResolver.GoToAnalysisSettings(newAnalysisViewModel);
}
/// <summary>Notifies command to update CanExecute property.</summary>
private void updateCanExecuteChanged(object sender, NotifyCollectionChangedEventArgs e)
{
CanExecuteChanged(this, new EventArgs());
}
}
Any suggestion what can i do to use that object from that thread is very useful.
The reason of the crash is likely because the network event doesn't happen on the GUI thread.
The call to CanExecuteChanged goes for modification of the button that is GUI object.
But GUI objects can only be modified on the GUI thread.
A quick fix :
public class NewAnalysisCommand : ICommand
{
// ...
private Dispatcher dispatcher;
public NewAnalysisCommand()
{
// The command captures the dispatcher of the GUI Thread
dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
}
private void updateCanExecuteChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// With a little help of the disptacher, let's go back to the gui thread.
dispatcher.Invoke( () => {
CanExecuteChanged(this, new EventArgs()); }
);
}
}
Regards

WinForms App w/BackgroundWorker & Class Object Events

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.
});
}
}

variable not set by background worker on first pass

I have an observer with a background worker. Lets say the observer has the following structure:
internal class Observer
{
private readonly BackgroundWorker bw1;
internal Object target;
public Observer()
{
bw1 = new BackgroundWorker();
bw1.DoWork += bw1_DoWork;
bw1.RunWorkerCompleted += bw1_RunWorkerCompleted;
bw1.WorkerSupportsCancellation = true;
}
private void bw1_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = new object(); // Query to database
}
private void bw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
target = e.Result as object;
if (ChangedScannedValue != null)
{
ChangedScannedValue(_scannedValue);
}
}
private String _scannedValue = string.Empty;
internal delegate void OnChangedScannedValue(String scannedValue);
internal event OnChangedScannedValue ChangedScannedValue;
internal String ScannedValue
{
get { return _scannedValue; }
set
{
_scannedValue = value;
bw1.RunWorkerAsync(_scannedValue);
//ProcessScannedValue();
}
}
}
I have another class listening to the event.
public partial class myControl : UserControl
{
Observer _observer = new Observer();
public myControl()
{
InitializeComponent();
}
internal void LoadData(Observer observer)
{
_observer = observer;
_observer.ChangedScannedValue += _observer_ChangedScannedValue;
}
void _observer_ChangedScannedValue(string ScannedValue)
{
if (_observer.target != null)
{
// Do Stuff
}
else
{
MessageBox.Show("NO TARGET FOUND.");
}
}
}
The thing is. initially, after the background worker finishes, I get the message box "NO TARGET FOUND.", however immediatley after, it would //Do Stuff Debugging shows that the RunWorkerCompleted event fires twice. This only happens on the first change to scanned value, all changes afterwards work as desired.
Questions:
1) Why does RunWorkerCompleted fire twice?
2) Why is the target not updated on the first fire of RunWorkerCompleted
You could try again with target being set in bw1_DoWork already, i.e.:
private void bw1_DoWork(object sender, DoWorkEventArgs e) {
target = new object(); // Query to database
}
private void bw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (ChangedScannedValue != null) {
ChangedScannedValue(_scannedValue);
}
}
Eventually you may want to replace the BackgroundWorker by a simpler solution with ThreadPool. I'd suggest this:
internal String ScannedValue {
get { return _scannedValue; }
set {
_scannedValue = value;
ThreadPool.QueueUserWorkItem( (WaitCallback) delegate {
target = new object(); // query database
if (ChangedScannedValue != null) ChangedScannedValue(_scannedValue);
} );
}
}

Categories

Resources