ICommand exception, thread cannot access the object - c#

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

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

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.

Report progress in client/server environment

I have a strange problem when reporting progress of the long running server operation.
The application has client/server architecture and written in C#. Client uses WPF.
On client side I create progress window and start in background worker a long running operation. This operation is a server method called via remoting. As argument server method accepts special ProgressContext object that is used to report progress (see code below).
As soon as server starts performing some heavy operations that utilize CPU/Memory - the progress window becomes frozen. Its not responding to any interactions and do not update progress. After a while when heavy operations are done - the progress window comes back to live like nothing happened.
It looks like when I pass instance of background worker to the server and server thread is heavy loaded - it some how locks the window backgroundworker is related to. If I use the same progress window without remoting calls - problem dissapears.
To report progress I use progress window with backgroundworker as in many samples around the web.
here is C# code for the progress window:
public partial class ProgressWindow : Window
{
#region Fields
public static readonly DependencyProperty AutoIncrementProperty =
DependencyProperty.Register(
"AutoIncrement",
typeof(bool),
typeof(ProgressBar),
new UIPropertyMetadata(null));
private readonly BackgroundWorker m_worker;
private CultureInfo m_culture;
private bool m_isCancelled;
private Exception m_error = null;
private Action<IProgressContext> m_workerCallback;
#endregion
#region Constructors
/// <summary>
/// Inits the dialog without displaying it.
/// </summary>
public ProgressWindow()
{
InitializeComponent();
//init background worker
m_worker = new BackgroundWorker();
m_worker.WorkerReportsProgress = true;
m_worker.WorkerSupportsCancellation = true;
m_worker.DoWork += Worker_DoWork;
m_worker.ProgressChanged += Worker_ProgressChanged;
m_worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
AutoIncrement = true;
CancellingEnabled = false;
}
#endregion
#region Public Properties
public bool CancellingEnabled
{
get
{
return btnCancel.IsVisible;
}
set
{
btnCancel.Visibility = value ? Visibility.Visible : Visibility.Collapsed;
}
}
public bool Cancelled
{
get
{
return m_isCancelled;
}
}
public bool AutoIncrement
{
get
{
return (bool)this.GetValue(AutoIncrementProperty);
}
set
{
this.SetValue(AutoIncrementProperty, value);
}
}
public Exception Error
{
get
{
return m_error;
}
}
#endregion
#region Public Methods
public void Run(Action<IProgressContext> action)
{
if (AutoIncrement)
{
progressBar.IsIndeterminate = true;
}
//store the UI culture
m_culture = CultureInfo.CurrentUICulture;
//store reference to callback handler and launch worker thread
m_workerCallback = action;
m_worker.RunWorkerAsync();
//display modal dialog (blocks caller)
ShowDialog();
}
#endregion
#region Private Methods
#region Event Handlers
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
//make sure the UI culture is properly set on the worker thread
Thread.CurrentThread.CurrentUICulture = m_culture;
ProgressContext context = new ProgressContext((BackgroundWorker)sender);
//invoke the callback method with the designated argument
m_workerCallback(context);
}
catch (Exception)
{
//disable cancelling and rethrow the exception
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(SendOrPostCallback)delegate { btnCancel.SetValue(Button.IsEnabledProperty, false); },
null);
throw;
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
btnCancel.IsEnabled = false;
m_worker.CancelAsync();
m_isCancelled = true;
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage != int.MinValue)
{
progressBar.Value = e.ProgressPercentage;
}
if (e.UserState != null)
{
lblStatus.Text = (string)e.UserState;
}
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
m_error = e.Error;
}
//update UI in case closing the dialog takes a moment
btnCancel.IsEnabled = false;
Close();
}
#endregion
#endregion
}
public class ProgressContext : MarshalByRefObject, IProgressContext
{
#region Fields
private BackgroundWorker m_worker;
#endregion
#region Constructors
public ProgressContext(BackgroundWorker worker)
{
m_worker = worker;
}
#endregion
#region Public Properties
public void ReportProgress(string message)
{
m_worker.ReportProgress(int.MinValue, message);
}
public void ReportProgress(int progress, string message)
{
m_worker.ReportProgress(progress, message);
}
public void ReportProgress(int progress)
{
m_worker.ReportProgress(progress);
}
public bool IsCancelled
{
get
{
return m_worker.CancellationPending;
}
}
#endregion
}
Any help will be appreciated. Thanks in advance.
I suspect the Backgroundworker is not fit for being marshaled using remoting this way.
Leave the Backgroundworker at the client, do not pass it and setup an event sink that is a MarshalByRefObject which remains on the client and is called/signaled from the server.
The sink in its turn can invoke methods on the Backgroundworker.
Thanks everyone for the input.
The reason for the problem was another process that in different thread was accessing server methods via its own Dispatcher.Invoke and causing locks. This process startups were rare - thus it made an impression of locking up after a while.
The overall recommendation I can give is to make Dispatcher.Invoke/BeginInvoke methods as light as possible without any heavy calculations inside. Do your server job beforehand and use them just to update the UI.

Categories

Resources