I'm developing an application with windows-10-t platform on raspberry-pi3. The application has several pages and listens GPIO ports asyncrhonously in the background. It collects data from GPIO and sends to the WCF-Service, after a bit the UI should be updated by the data coming from the WCFService. I've also tried using Tasks, Dispatcher.Invoke etc. but nothing worked properly. I can collect data coming from GPIO but cannot update UI. What am I doing wrong?
Here is the background GPIO listener class with static variables (I'm listening GPIO in other pages too.):
public sealed class GPIO{
private static MainPage mainpage;
public static event EventHandler ProgressUpdate;
public static void InitGPIO(MainPage sender)
{
mainpage = sender;
DataPin.DebounceTimeout = TimeSpan.FromMilliseconds(50);
DataPin.ValueChanged += DataPin_ValueChanged;
}
public static void DataPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs e)
{
if (e.Edge == GpioPinEdge.FallingEdge)
{
Task.Run(() => AddData(0));
}
}
public static async void AddData(int prm_Data)
{
// WCF-Service Operation
await Service.wsClient.GPIOValueAddition(prm_Data);
GPIO.ProgressUpdateOperation();
}
private static void ProgressUpdateOperation()
{
mainpage.GPIO_ProgressUpdate(typeof(GPIO), new EventArgs());
}
}
And here is the page that contains the UI to be updated:
public sealed partial class MainPage : Page
{
public MainPage()
{
GPIO.InitGPIO(this);
GPIO.ProgressUpdate += GPIO_ProgressUpdate;
}
public void GPIO_ProgressUpdate(object sender, EventArgs e)
{
// WCF-Service Operation
service_data = (int)Service.wsClient.GetDataFromServicetoUpdateUI(parameter).Result;
// UI-update
txtUpdate.Text = service_data.ToString();
}
}
EDIT: I forgot to add the exception. "The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))" exception is thrown at AddData function called in DataPin_Valuechanged.
I found the solution in here : https://stackoverflow.com/a/27698035/1093584
Here is the new update-UI function :
public void GPIO_ProgressUpdate(object sender, EventArgs e)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
service_data = await Service.wsClient.GetDataFromServicetoUpdateUI(parameter);
// UI-update
txtUpdate.Text = service_data.ToString();
});
}
Related
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.
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
I currently have an installation "framework" that does specific things. What I need now to do is be able to call my form in parallel with my script. Something like this:
InstallationForm f = new InstallationForm();
Application.Run(f);
InstallSoftware(f);
private static void InstallSoftware(InstallationForm f) {
f.WriteToTextbox("Starting installation...");
Utils.Execute(#"C:\temp\setup.msi", #"-s C:\temp\instructions.xml");
...
f.WriteToTextbox("Installation finished");
The current way I can do this is by adding the Form.Shown handler in InstallSoftware, but that seems really messy. Is there anyway I can do this better?
Your code will not work, because Application.Run(f) returns not until the form was closed.
You may use a simplified Model/View/Controller pattern. Create an InstallationFormController class that has several events, e.g. for textual notifications to be written to your textbox. The InstallationForm registers on these events in it's OnLoad() method and then calls InstallationFormController.Initialize(). That method starts your installation (on a worker thread/task). That installation callback method fires several text events.
InstallationForm f = new InstallationForm(new InstallationFormController());
Application.Run(f);
internal class InstallationFormController
{
public event EventHandler<DataEventArgsT<string>> NotificationTextChanged;
public InstallationFormController()
{
}
public void Initialize()
{
Task.Factory.StartNew(DoInstallation);
}
private void DoInstallation()
{
...
OnNotificationTextChanged(new DataEventArgsT<string>("Installation finished"));
}
private void OnNotificationTextChanged(DataEventArgsT<string> e)
{
if(NotificationTextChanged != null)
NotificationTextChanged(this, e);
}
}
public class DataEventArgsT<T> : EventArgs
{
...
public T Data { get; set; }
}
internal class InstallationForm : Form
{
private readonly InstallationFormController _controller;
public InstallationForm()
{
InitializeComponent();
}
public InstallationForm(InstallationFormController controller) : this()
{
if(controller == null)
throw new ArgumentNullException("controller")
_controller = controller;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_controller.NotificationTextChanged += Controller_NotificationTextChanged;
_controller.Initialize();
}
protected virtual void Controller_NotificationTextChanged(object sender, DataEventArgsT<string> e)
{
if(this.InvokeRequired)
{ // call this method on UI thread!!!
var callback = new EventHandler<DataEventArgsT<string>>(Controller_NotificationTextChanged);
this.Invoke(callback, new object[] {sender, e});
}
else
{
_myTextBox.Text = e.Data;
}
}
...
}
I just wrote a simple C# to get an eventcallback from PCI-7250 (Data Acquisition Card) when any of the digital inputs go high. Here is my code:
public delegate void ReadDelegate(uint value)
public void Form1_Load(object sender, EventArgs e)
{
m_dev = DASK.Register_Card(DASK.PCI_7250,0);
ReadDelegate ReadNow = new ReadDelegate(FunctionToCall)
DASK.DI_EventCallBack((ushort)m_dev,1,(short)DASK.DBEvent,ReadNow)
}
private void FunctionToCall(uint int_value)
{
MessageBox.Show(int_value)
}
When run it just keep"s up throwing some random numbers during runtime and then finally crashes. I believe it has something to do with the EventType (DASK.DBEvent). I went through the manual but nothing more is mentioned about the DASK.DBEvent.
Kindly please advise.
Since the device doesn't have support for callbacks in its driver, you could adapt the driver's API from synchronous calls to callbacks by polling the device in a background thread.
First, create a class that polls the device for the physical event you're interested in responding to. Then, in your GUI code, put the polling work in the background and respond to the callback in the main thread.
I'm not familiar with the ADLink driver, so I'll just summarize a design approach and sketch some pseudocode that isn't threadsafe. This is a naive approach to using Tasks, but you could also update it to use continuations or async/await. Or, if you need more than one responder, make the callback raise an event that your other classes can subscribe to.
Polling class
public class EdgeDetector
{
public delegate void OnRisingEdgeDetected(uint currentValue, uint linesThatAsserted);
private bool m_shouldPoll;
private void PollForRisingEdge(PCI_7250 device, OnRisingEdgeDetected onRisingEdgeDetected)
{
while (m_shouldPoll)
{
// Optional: sleep to avoid consuming CPU
uint newPortValue = device.ReadAllDigitalLines();
uint changedLines = m_currentPortValue ^ newPortValue;
uint risingEdges = newPortValue & changedLines;
m_currentPortValue = newPortValue;
if (risingEdges != 0)
{
onRisingEdgeDetected(currentValue: newPortValue,
linesThatAsserted: risingEdges);
}
}
public void Start(PCI_7250 device, OnRisingEdgeDetected onRisingEdgeDetected)
{
m_shouldPoll = true;
PollForRisingEdge(device, onRisingEdgeDetected);
}
public void Stop()
{
m_shouldPoll = false;
}
}
WinForm class
private void Initialize()
{
m_dev = DASK.Register_Card(DASK.PCI_7250, 0);
m_mainThreadScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
private void StartEdgeDetection()
{
m_edgeDetectionTask = Task.Factory.StartNew( () =>
{
m_edgeDetector.Start(device: m_dev, onRisingEdgeDetected: RescheduleOnMainThread);
});
}
private RescheduleOnMainThread(uint currentValue, uint linesThatAsserted)
{
m_onEdgeDetectionTask = Task.Factory.StartNew(
action: () =>
{
MessageBox.Show(currentValue);
},
cancellationToken: null,
creationOptions: TaskCreationOptions.None,
scheduler: m_mainThreadScheduler);
}
private void CleanUp()
{
m_edgeDetector.Stop();
m_edgeDetectionTask.Wait();
m_onEdgeDetectionTask.Wait();
}
public void Form1_Load(object sender, EventArgs e)
{
Initialize();
StartEdgeDetection();
}
public void Form1_Closed(object sender, EventArgs e)
{
CleanUp();
}
I have a Task that I've launched to handle a long running imaging operation. Within this task, I fire an custom event. I'm trying to subscribe to this event in my MainWindow (UI thread) to update some data on the UI.
I found what I thought was a example of how to do this (Dispatcher to invoke event handler), but get a compiler error (No overload for 'Imager_CameraEvent' matches delegate 'System.Action') inside the event handler at the point marked below.
Suggestions, corrections, and/or admonishments for a poor approach are welcome.
MainWindow:
public partial class MainWindow : Window
{
Imager m_imager;
public MainWindow()
{
InitializeComponent();
m_imager = new Imager();
m_imager.m_cameraEvent += Imager_CameraEvent;
}
private void RunImager_Click(object sender, RoutedEventArgs e)
{
// this task can create Camera Events
Task ImagingTask = Task.Factory.StartNew(() => m_imager.StartImaging(m_cancelToken), m_cancelToken);
}
void Imager_CameraEvent(object sender, CameraEventArgs e)
{
// Camera Events handled here
if (this.Dispatcher.CheckAccess())
{
MainMessageWindow.AppendText(e.Message);
}
else
{
this.Dispatcher.BeginInvoke(new Action(Imager_CameraEvent), sender, e); //<-- Compiler ERROR here
}
}
}
Imager Class (has method run in Task):
public delegate void CameraEventHandler(object sender, CameraEventArgs e);
public class Imager
{
public event CameraEventHandler m_cameraEvent;
protected virtual void OnCameraEvent(CameraEventArgs e)
{
m_cameraEvent(this, e);
}
public async void StartImaging(CancellationToken cancelToken)
{
ImagingParams iParams = new ImagingParams();
// set up iParams ...
/// Start Imaging Task
Task<int> ImagingTask = Task.Factory.StartNew<int>(() => ImageReader_worker(iParams, cancelToken), cancelToken);
try
{
imageCount = await ImagingTask;
}
catch (OperationCanceledException)
{
OnCameraEvent(new CameraEventArgs("Imaging Cancelled"));
}
catch (Exception ex)
{
OnCameraEvent(new CameraEventArgs(ex.Message));
}
finally
{
ImagingTask.Dispose();
}
}
}