To explain this problem i put everything needed into a small sample application which hopefully explains the problem. I really tried to push everything in as less lines as possible, but in my real application these different actors don't know each other and also shouldn't. So, simple answer like "take the variable a few lines above and call Invoke on it" wouldn't work.
So let's start with the code and afterwards a little more explanation. At first there is a simple class that implements INotifyPropertyChanged:
public class MyData : INotifyPropertyChanged
{
private string _MyText;
public MyData()
{
_MyText = "Initial";
}
public string MyText
{
get { return _MyText; }
set
{
_MyText = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyText"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So nothing special about. And here the example code which can simply be put into any empty console application project:
static void Main(string[] args)
{
// Initialize the data and bindingSource
var myData = new MyData();
var bindingSource = new BindingSource();
bindingSource.DataSource = myData;
// Initialize the form and the controls of it ...
var form = new Form();
// ... the TextBox including data bind to it
var textBox = new TextBox();
textBox.DataBindings.Add("Text", bindingSource, "MyText");
textBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
textBox.Dock = DockStyle.Top;
form.Controls.Add(textBox);
// ... the button and what happens on a click
var button = new Button();
button.Text = "Click me";
button.Dock = DockStyle.Top;
form.Controls.Add(button);
button.Click += (_, __) =>
{
// Create another thread that does something with the data object
var worker = new BackgroundWorker();
worker.RunWorkerCompleted += (___, ____) => button.Enabled = true;
worker.DoWork += (___, _____) =>
{
for (int i = 0; i < 10; i++)
{
// This leads to a cross-thread exception
// but all i'm doing is simply act on a property in
// my data and i can't see here that any gui is involved.
myData.MyText = "Try " + i;
}
};
button.Enabled = false;
worker.RunWorkerAsync();
};
form.ShowDialog();
}
If you would run this code you would get a cross-thread exception by trying to change the MyText property. This comes, cause the MyData object calls PropertyChanged which will be catched by the BindindSource. This will then, according to the Binding, try to update the Text property of the TextBox. Which clearly leads to the exception.
My biggest problem here comes from the fact that the MyData object shouldn't know anything about a gui (cause it is a simple data object). Also the worker thread doesn't know anything about a gui. It simply acts on a bunch of data objects and manipulates them.
IMHO i think the BindingSource should check on which thread the receiving object is living and do an appropiate Invoke() to get the value their. Unfortunately this isn't built into it (or am i wrong?), so my question is:
How can resolve this cross-thread exception if the data object nor the worker thread know anything about a binding source that is listening for their events to push the data into a gui.
Here is the part of the above example that solves this problem:
button.Click += (_, __) =>
{
// Create another thread that does something with the data object
var worker = new BackgroundWorker();
worker.DoWork += (___, _____) =>
{
for (int i = 0; i < 10; i++)
{
// This doesn't lead to any cross-thread exception
// anymore, cause the binding source was told to
// be quiet. When we're finished and back in the
// gui thread tell her to fire again its events.
myData.MyText = "Try " + i;
}
};
worker.RunWorkerCompleted += (___, ____) =>
{
// Back in gui thread let the binding source
// update the gui elements.
bindingSource.ResumeBinding();
button.Enabled = true;
};
// Stop the binding source from propagating
// any events to the gui thread.
bindingSource.SuspendBinding();
button.Enabled = false;
worker.RunWorkerAsync();
};
So this doesn't lead to any cross-thread exceptions anymore. The drawback of this solution is that you won't get any intermediate results shown within the textbox, but it's better than nothing.
I realize that your question was posed some time ago, but I've decided to submit an answer just in case it's helpful to someone out there.
I suggest you consider subscribing to myData's property changed event within your main application, then updating your UI. Here's what it might look like:
//This delegate will help us access the UI thread
delegate void dUpdateTextBox(string text);
//You'll need class-scope references to your variables
private MyData myData;
private TextBox textBox;
static void Main(string[] args)
{
// Initialize the data and bindingSource
myData = new MyData();
myData.PropertyChanged += MyData_PropertyChanged;
// Initialize the form and the controls of it ...
var form = new Form();
// ... the TextBox including data bind to it
textBox = new TextBox();
textBox.Dock = DockStyle.Top;
form.Controls.Add(textBox);
// ... the button and what happens on a click
var button = new Button();
button.Text = "Click me";
button.Dock = DockStyle.Top;
form.Controls.Add(button);
button.Click += (_, __) =>
{
// Create another thread that does something with the data object
var worker = new BackgroundWorker();
worker.RunWorkerCompleted += (___, ____) => button.Enabled = true;
worker.DoWork += (___, _____) =>
{
for (int i = 0; i < 10; i++)
{
myData.MyText = "Try " + i;
}
};
button.Enabled = false;
worker.RunWorkerAsync();
};
form.ShowDialog();
}
//This handler will be called every time "MyText" is changed
private void MyData_PropertyChanged(Object sender, PropertyChangedEventArgs e)
{
if((MyData)sender == myData && e.PropertyName == "MyText")
{
//If we are certain that this method was called from "MyText",
//then update the UI
UpdateTextBox(((MyData)sender).MyText);
}
}
private void UpdateTextBox(string text)
{
//Check to see if this method call is coming in from the UI thread or not
if(textBox.RequiresInvoke)
{
//If we're not on the UI thread, invoke this method from the UI thread
textBox.BeginInvoke(new dUpdateTextBox(UpdateTextBox), text);
return;
}
//If we've reached this line of code, we are on the UI thread
textBox.Text = text;
}
Granted, this does away with the binding pattern you were trying before. However every update to MyText should be received and displayed without issue.
You can't update the BindingSource from another thread if it's bound to a winforms control. In your MyText setter you must Invoke PropertyChanged on the UI thread rather than running it directly.
If you want an extra layer of abstraction between your MyText class and the BindingSource you can do that, but you can't separate the BindngSource from the UI thread.
In Windows Froms
In cross thread i just used
// this = form on which listbox control is created.
this.Invoke(new Action(() =>
{
//you can call all controls it will not raise exception of cross thread
//example
SomeBindingSource.ResetBindings(false);
Label1.Text = "any thing"
TextBox1.Text = "any thing"
}));
and VOILA
/////////// Edit //////////
If there is chance of call from same thread it is created on then add following check
// this = form on which listbox control is created.
if(this.InvokeRequired)
this.Invoke(new Action(() => { SomeBindingSource.ResetBindings(false); }));
else
SomeBindingSource.ResetBindings(false);
You can try reporting progress from the background thread which will rise an event in the UI thread. Alternatively, you can try remembering the current context (your UI thread) before calling DoWork and then inside the DoWork you can use the remembered context to post data.
I know this is an old post, but I just ran into this issue on a winforms app and this seemed to work.
I made a subclass of BindingSource and intercepted the OnListChanged handler to invoke on the UI thread.
public class MyBindingSource : BindingSource
{
private readonly ISynchronizeInvoke context;
protected override void OnListChanged(ListChangedEventArgs e)
{
if (context == null) base.OnListChanged(e);
else context.InvokeIfRequired(c => base.OnListChanged(e));
}
public MyBindingSource(ISynchronizeInvoke context = null)
{
this.context = context;
}
}
Where InvokeIfRequired is the handy extension method mentioned by a few others in this post.
Related
I´m using the BusyIndicator in my GUI because I have to work with a database that the GUI don´t freeze meanwhile.
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
richSammelbemerkung.Document.Blocks.Clear();
richSammelbemerkung.AppendText("Daten werden gesucht...");
GUIData guiData = new GUIData();
guiData = getInfoFromGUI();
ZeichnungCollection zeichnungen = new ZeichnungCollection();
BackgroundWorker worker = new BackgroundWorker();
busyIndicator.IsBusy = true;
worker.DoWork += (o, ea) =>
{
zeichnungen = searchDrawings(guiData);
};
worker.RunWorkerCompleted += (o, ea) =>
{
Application.Current.Dispatcher.Invoke((Action)(() => CollectionViewSource.GetDefaultView(dataOutOfDb.ItemsSource = zeichnungen).Refresh()));
busyIndicator.IsBusy = false;
if (zeichnungen.Count == 0)
{
MessageBox.Show("Keine Daten gefunden. Eventuell Index überprüfen.", "Info");
}
richSammelbemerkung.Document.Blocks.Clear();
dataOutOfDb.SelectedIndex = 0;
Keyboard.Focus(dataOutOfDb);
};
busyIndicator.IsBusy = true;
worker.RunWorkerAsync();
}
It´s look like this.
Before I put the BusyIndicator into my GUI I just run the code and used the SelectionChanged of the Cmb to set the selected Text into a Textfield.
The problem I have now is, that when the SelectionChanged is fired it throws a Exception even if I used a IF to ask for elements.
So I went on with DataBinding like this:
Text="{Binding ElementName=cmbTag, Path=SelectedItem}"
Now when it doesn´t throw a exception or anything else.
BUT, I can´t set a new value in the Textfield because it automatically refreshes on what is selected in the Combobox.
So, has anyone an idea how I can set the selected value from the Cmb to the Textfield without using SelectionChanged or DataBinding, or even so that it doens´t throw a exception?
This has nothing to do with the BusyIndocator.
The problem is that you want to access the UI from another thread. But any operation with the UI must be made from the UI-Thread.
In your example anything within the DoWork method (which runs in another thread) can not access the UI. You'll get an exception or Bindings won't work.
You can use a Dispatcher within the DoWork method to track the code back to the UI Thread, like this:
Application.Current.Dispatcher.Invoke(() => {
// this code will run in the UI Thread again
});
But I think it's more elegant to remove all UI manipulation from the DoWork Action or use the BackgroundWork's ReportProgress Method for that.
I'm facing some issues when I'm trying to deal with IBindingList inherited collections that are modified by a non-UI Thread and are at the same time bound to the DataSource Property of some Winforms Controls (e.g. an old fashion DataGridView).
My point is that Invoke / Begin are not suitable in my solution, cause the collections are not supposed to be really aware of the controls they are bound to.
Is there any other elegant solution than passing the UI Dispatcher or the appropriate SynchronizationContext reference to the collection which is going to be modified later on?
I was thinking about how one can detect whether that the current Thread is the UI accessing to the collection? If could know that,
therefore I could select to dispatch or not the collection modifications to the UI Thread. This solution still does not prevent to pass the reference of the UI SynchronizationContext or Dispatcher, though.
It would be nice to have real static properties which could give access, instead we have Dispatcher.CurrentDispatcher and WinformsSynchronizationContext that actually cannot be accessed from another thread... don't really get the point of the design if we still need to pass the reference from the UI Thread then. But I guess there is some reasons behind the scenes.
I may misunderstood and twist the definitions of some terms, so feel free to correct me if that's the case. I do not want to pretend that I'm expert or whatsoever.
The fact that delegating systematically everything to the UI seems a bit too much and might somehow overload the message pump cause in this scenario it would be better to create the collection right beforehand within the UI scope but then it would loose the interest of delegating the modifications among several threads.
I know that IBindingList is going to reflect through ListChanged (which is subscribed via the Control bound to a IBindingList inherited collection) the collection changes.
Basically those changes are going to be triggered by the non-UI Thread and that basically the very bottom line. Although, I'm bit frustrated by the lack of elegant solutions.
By the way, I am not willing to use the async/await and neither some special events of the BackgroundWorker that automatically Marhsall to the UI Thread when they are triggered. Maybe I should have a look at http://referencesource.microsoft.com/
Please find below the results of my investigations about the UI Marshalling:
public static class Program
{
[STAThread]
public static void Main(params String[] arguments)
{
var bindingList = new BindingList<Record>();
Action<Object> action = (argument) =>
{
var synchronizationContext = argument as SynchronizationContext;
var dispatcher = argument as Dispatcher;
if ((synchronizationContext == null) && (dispatcher == null))
{
throw new ArgumentException(#"argument");
}
else
{
var random = new Random();
while (true)
{
Action<Object> sendOrPostCallbackAction = (state) =>
{
var record = new Record();
bindingList.Add(record);
};
var sendOrPostCallback = new SendOrPostCallback(sendOrPostCallbackAction);
// WindowsFormsSynchronizationContext.Current.Send(sendOrPostCallback, new Object());
// *** NullReferenceException
// *** Object reference not set to an instance of an object...
// *** Thought this could be found not matter where.
// *** Cause the winforms application is already started at that point in the code.
// Dispatcher.CurrentDispatcher.Invoke(sendOrPostCallback, (Object)null);
// *** InvalidOperationException
// *** Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
if (dispatcher != null)
{
// Only Works if the synchronizationContext is the one used by the UI Thread...
dispatcher.Invoke(sendOrPostCallback, (Object)null);
}
else
{
// Only Works if the synchronizationContext is the one used by the UI Thread...
synchronizationContext.Send(sendOrPostCallback, (Object)null);
}
Thread.Sleep(100);
}
}
};
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new MainForm
{
DataGridView = { DataSource = bindingList },
};
mainForm.ButtonPassSynchronizationContext.Click += (sender, args) =>
{
mainForm.ButtonPassSynchronizationContext.Enabled = false;
mainForm.ButtonPassDispatcher.Enabled = false;
// Of course here the synchronization context is the one used by the UI Thread...
// WindowsFormsSynchronizationContext.Current should be equal here.
var areTheSameSyncrhonizationContexts = SynchronizationContext.Current == WindowsFormsSynchronizationContext.Current;
Debug.WriteLineIf(areTheSameSyncrhonizationContexts, #"The sychronization contexts are the same.");
Task.Factory.StartNew(action, SynchronizationContext.Current);
};
mainForm.ButtonPassDispatcher.Click += (sender, args) =>
{
mainForm.ButtonPassSynchronizationContext.Enabled = false;
mainForm.ButtonPassDispatcher.Enabled = false;
// The dispatcher is indeed not null, since it is the one used by the UI Thread...
// Dispatcher.CurrentDispatcher should be not null here.
var isDispatcherNotNull = Dispatcher.CurrentDispatcher != null;
Debug.WriteLineIf(isDispatcherNotNull, #"The Current Dispatcher is not null.");
Task.Factory.StartNew(action, Dispatcher.CurrentDispatcher);
};
Application.Run(mainForm);
}
public class Record
{
public Record()
{
var random = new Random();
this._dummyProperty = random.Next(0, 100);
}
private readonly Int32 _dummyProperty;
public Int32 DummyProperty
{
get { return this._dummyProperty; }
}
}
public class MainForm : Form
{
public MainForm()
{
this.InitializeComponent();
}
public Button ButtonPassSynchronizationContext { get; private set; }
public Button ButtonPassDispatcher { get; private set; }
public DataGridView DataGridView { get; private set; }
private void InitializeComponent()
{
this.Text = #"Main Form";
this.StartPosition = FormStartPosition.CenterScreen;
this.DataGridView = new DataGridView
{
Dock = DockStyle.Fill,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
DefaultCellStyle = { Alignment = DataGridViewContentAlignment.MiddleCenter },
AlternatingRowsDefaultCellStyle = { BackColor = Color.LightGoldenrodYellow },
};
this.ButtonPassSynchronizationContext = new Button()
{
Dock = DockStyle.Bottom,
Text = #"Pass SynchronizationContext",
};
this.ButtonPassDispatcher = new Button()
{
Dock = DockStyle.Bottom,
Text = #"Pass Dispatcher",
};
this.Controls.Add(this.DataGridView);
this.Controls.Add(this.ButtonPassSynchronizationContext);
this.Controls.Add(this.ButtonPassDispatcher);
}
}
}
I am trying to discover the best way of avoiding UI-Lockups when you are doing a lot of updates to the UI at once.
The basic premise is that on start up my tool runs a perforce FSTAT on the within a background worker. This generates a very large list of files and their information. Once this is completed, in its RunWorkerCompleted function, I then propagate this information to the UI inside of a TreeView.
This however, involves lots of property updates! Depending on the number of files that its propagating to. It can be 5000+ files. This completely locks up the UI for about 3-5 seconds.
I was wondering if I can asynchronously update the UI, such that I say, propagate 10-20 files at once & Still let the UI thread continue to update so that its still responsive.
Thank you.
If you are updating information inside of the TreeView using property bindings you could set your Binding.IsAsync flag to true. If you aren't updating the values using bindings then that might be something to look into.
Binding.IsAsync Property
Another option would be to update all your properties but, to not call the PropertyChanged event for the property (Assuming you are using INotifyPropertyChanged to update your bindings) until all your data has been changed and then call the PropertyChanged event for each of your properties on a Task so, it is still Async but even with 5000+ binding updates it should not take 3-5 seconds.
lots of suggestions you made finally got me to a good answer. Here is my code below. Basically we can use ReportProgress to allow the UI to update-while-running. Then adjust for how often we want this to happen. Here is my solution below.
The key is that PropegateMetaData is called for every N number of items (I specified 25). Then the list is emptied.
This will call report progress for every 25 items, then continue on. And eventually pass the rest to WorkerCompleted.
public static void Refresh(List<string> refreshPaths)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
List<string> filesPath = null;
if (refreshPaths == null)
{
filesPath = DatabaseViewModel.Instance.Records.Select(record => record.Filepath).ToList();
}
else
{
filesPath = new List<string>(refreshPaths);
}
if (m_Repository != null && filesPath.Count > 0)
{
IList<FileSpec> lfs = new List<FileSpec>();
int index = 0;
foreach (DataRecord rec in DatabaseViewModel.Instance.Records)
{
lfs.Add(new FileSpec(new LocalPath(rec.Filepath), null));
index++;
if (index > MaxFilesIteration)
{
GetFileMetaDataCmdOptions opts = new GetFileMetaDataCmdOptions(GetFileMetadataCmdFlags.AllRevisions, null, null, 0, null, null, null);
worker.ReportProgress(0, m_Repository.GetFileMetaData(lfs, null));
lfs.Clear();
index = 0;
}
}
args.Result = m_Repository.GetFileMetaData(lfs, null); //pass the remaining results across
}
};
worker.ProgressChanged += (sender, args) => PropegateMetaData(args.UserState as IList<FileMetaData>);
worker.RunWorkerCompleted += (sender, args) => PropegateMetaData(args.Result as IList<FileMetaData>);
worker.RunWorkerAsync();
}
private static void PropegateMetaData(IList<FileMetaData> fileList)
{
IList<FileMetaData> fileState = fileList as IList<FileMetaData>;
if (fileState != null)
{
foreach (FileMetaData fmd in fileState)
{
DataRecord currentRecord = DatabaseViewModel.Instance.GetRecordByFilepath(fmd.LocalPath.Path);
if (currentRecord != null)
{
switch (fmd.Action)
{
case FileAction.Add:
currentRecord.P4Status = P4FileState.Added;
break;
case FileAction.Edit:
currentRecord.P4Status = P4FileState.Edit;
break;
case FileAction.MoveAdd:
currentRecord.P4Status = P4FileState.MoveAdd;
break;
default:
currentRecord.P4Status = P4FileState.None;
break;
}
}
}
}
}
Basically, this is what happens. I have a thread(endless loop) that runs as a background process while the form is showing. The thread checks if there is a need to add a new ToolStripMenuItem.
If the conditions are met, I'll need to use Invoke in order to create the UI object right? Problem with this is, when the this.Invoke or BeginInvoke is called, the form became unresponsive while the thread that does the checking is still running fine. Any ideas?
This is the first time i'm trying with this multithreading thingee. I'm sure i've missed out something.
public void ThreadSetCom()
{
while (true)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = p;
tsmi.Name = p;
tsmi.Click += new EventHandler(itm_Click);
if (this.msMenu.InvokeRequired)
{
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
}
else
{
cpDropdownList.DropDownItems.Add(tsmi);
}
}
}
}
Your ThreadSetCom method never exits:
while (true)
... with no return or break statements. That's going to hang the UI thread forever.
It's not clear what you're trying to achieve, but you definitely don't want to be looping like that in the UI thread. I'd argue that you don't want to be looping like that in a tight way in any thread, mind you...
I think a better approach for you would probably be to use a BackgroundWorker. I say that because what you're experiencing isn't that uncommon when doing multi-threading in a Windows Forms application. Further, the BackgroundWorker is able to manage the thread switching properly. Let me give you an example of that code with the BackgroundWorker.
Build a private class variable
private BackgroundWorker _worker;
Add to the CTOR
public {ctor}()
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.WorkerReportsProgress = true;
_worker.DoWork += new DoWorkEventHandler(BackgroundThreadWork);
_worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundThreadProgress);
}
DoWork handler
private void BackgroundThreadWork(object sender, DoWorkEventArgs e)
{
while (!_worker.CancellationPending)
{
string[] tmpStrPort = System.IO.Ports.SerialPort.GetPortNames();
IEnumerable<string> diff = tmpStrPort.Except(strPort);
strPort = tmpStrPort;
System.Console.WriteLine(System.IO.Ports.SerialPort.GetPortNames().Length);
foreach (string p in diff)
{
_worker.ReportProgress(1, p);
}
}
}
Report progress handler
private void BackgroundThreadProgress(object sender, ReportProgressEventArgs e)
{
var cpDropdown = (ToolStripMenuItem)msMenu.Items["connectToolStripMenuItem"];
cpDropdown = (ToolStripMenuItem)cpDropdown.DropDownItems["connectReaderToolStripMenuItem"];
ToolStripMenuItem tsmi = new ToolStripMenuItem();
tsmi.Text = e.UserState as string;
tsmi.Name = e.UserState as string;
tsmi.Click += new EventHandler(itm_Click);
cpDropdownList.DropDownItems.Add(tsmi);
}
The Loop
However, one thing you're going to have to do is figure out how to get out of this loop. When should it exit? Whatever that means, you need to add to the if statement that exists there in my example because this loop will never end otherwise.
What the effect of this code snippet:
GUIUpdate d = new GUIUpdate(ThreadSetCom);
this.Invoke(d);
is that the method 'ThreadSetCom' will be invoked in the UI thread. And there is an infinitive loop in that method. That is why your form becomes unresponsive.
I suggest you that you should move the foreach clause to a separate method and invoke this method in the UI thread when the condition is hit, for example the diff.Count>0.
I use BackgroundWorker most of the time in the win form apps to show progress as I'm getting data. I was under impression that Work_completed is guaranteed to be executed on Main UI thread but it's not. If we create a thread and call the worker.RunWorkerAsync within it, it breaks if we try to update any gui control. Here is an example
private void StartButton_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(new ThreadStart(PerformWorkerTask));
_worker = new BackgroundWorker();
thread1.Start();
}
public void PerformWorkerTask()
{
_worker.DoWork += delegate
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);
}
};
_worker.RunWorkerCompleted += delegate
{
// this throws exception
MessageLabel.Text = "Completed";
};
_worker.RunWorkerAsync();
}
How can we make backgroundworker work in this case?
RunWorkerAsync does its thread-synchronization magic by getting the SynchronizationContext from the thread that it is called on. It then guarantees that the events will be executed on the correct thread according to the semantics of the SynchronizationContext it got. In the case of the WindowsFormsSynchronizationContext, which is what is automatically used if you're using WinForms, the events are synchronized by posting to the message queue of the thread that started the operation. Of course, this is all transparent to you until it breaks.
EDIT: You MUST call RunWorkerAsync from the UI thread for this to work. If you can't do it any other way, your best bet is to invoke the beginning of the operation on a control so that the worker is started on the UI thread:
private void RunWorker()
{
_worker = new BackgroundWorker();
_worker.DoWork += delegate
{
// do work
};
_worker.RunWorkerCompleted += delegate
{
MessageLabel.Text = "Completed";
};
_worker.RunWorkerAsync();
}
// ... some code that's executing on a non-UI thread ...
{
MessageLabel.Invoke(new Action(RunWorker));
}
From your example it's hard to see what good the Thread (thread1) is, but if you really do need this thread1 then I think your only option is to use MainForm.Invoke() to execute RunWorkerAsync() (or a small method around it) on the main thread.
Added: You can use something like this:
Action a = new Action(_worker.RunWorkerAsync);
this.Invoke(a);
It sounds like the issue is just that you want to make a change to a GUI component and you aren't actually sure if you're on the GUI thread. Dan posted a valid method of setting a GUI component property safely, but I find the following shortcut method the simplest:
MessageLabel.Invoke(
(MethodInvoker)delegate
{
MessageLabel.Text = "Hello World";
});
If there are any issues with this approach, I'd like to know about them!
In the code you have presented here, you're adding the delegates for the BackgroundWorker events in a separate thread from the UI thread.
Try adding the event handlers in the main UI thread, and you should be okay.
You could probably make your existing code work by doing:
this.Dispatcher.BeginInvoke(() => MessageLabel.Text = "Completed")
instead of
MessageLabel.Text = "Completed"
You're probably having cross-thread data access issues, so you have to ensure that you access properties of MessageLabel on your UI thread. This is one way to do that. Some of the other suggestions are valid too. The question to ask yourself is: why are you creating a thread that does nothing other than create a BackgroundWorker thread? If there's a reason, then fine, but from what you've shown here there's no reason you couldn't create and start the BackgroundWorker thread from your event handler, in which case there would be no cross-thread access issue because the RunWorkerCompleted event handler will call its delegates on the UI thread.
I believe BackgroundWorker is designed to automatically utilize a new thread. Therefore creating a new thread just to call RunWorkerAsync is redundant. You are creating a thread just to create yet another thread. What's probably happening is this:
You create a new thread from thread 1 (the GUI thread); call this thread 2.
From thread 2, you launch RunWorkerAsync which itself creates yet another thread; call this thread 3.
The code for RunWorkerCompleted runs on thread 2, which is the thread that called RunWorkerAsync.
Since thread 2 is not the same as the GUI thread (thread 1), you get an illegal cross-thread call exception.
(The below suggestion uses VB instead of C# since that's what I'm more familiar with; I'm guessing you can figure out how to write the appropriate C# code to do the same thing.)
Get rid of the extraneous new thread; just declare _worker WithEvents, add handlers to _worker.DoWork and _worker.RunWorkerCompleted, and then call _worker.RunWorkerAsync instead of defining a custom PerformWorkerTask function.
EDIT: To update GUI controls in a thread-safe manner, use code like the following (more or less copied from this article from MSDN):
delegate void SetTextCallback(System.Windows.Forms.Control c, string t);
private void SafeSetText(System.Windows.Forms.Control c, string t)
{
if (c.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SafeSetText);
d.Invoke(d, new object[] { c, t });
}
else
{
c.Text = t;
}
}
The best way to deal with these generic problems is to deal it once. Here I'm posting a small class that wraps the backgroupdworker thread and makes sure that the workcompleted always gets executed on the UI thread.
using System.Windows.Forms;
namespace UI.Windows.Forms.Utilities.DataManagment
{
public class DataLoader
{
private BackgroundWorker _worker;
private DoWorkEventHandler _workDelegate;
private RunWorkerCompletedEventHandler _workCompleted;
private ExceptionHandlerDelegate _exceptionHandler;
public static readonly Control ControlInvoker = new Control();
public DoWorkEventHandler WorkDelegate
{
get { return _workDelegate; }
set { _workDelegate = value; }
}
public RunWorkerCompletedEventHandler WorkCompleted
{
get { return _workCompleted; }
set { _workCompleted = value; }
}
public ExceptionHandlerDelegate ExceptionHandler
{
get { return _exceptionHandler; }
set { _exceptionHandler = value; }
}
public void Execute()
{
if (WorkDelegate == null)
{
throw new Exception(
"WorkDelegage is not assinged any method to execute. Use WorkDelegate Property to assing the method to execute");
}
if (WorkCompleted == null)
{
throw new Exception(
"WorkCompleted is not assinged any method to execute. Use WorkCompleted Property to assing the method to execute");
}
SetupWorkerThread();
_worker.RunWorkerAsync();
}
private void SetupWorkerThread()
{
_worker = new BackgroundWorker();
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += WorkDelegate;
_worker.RunWorkerCompleted += worker_RunWorkerCompleted;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if(e.Error !=null && ExceptionHandler != null)
{
ExceptionHandler(e.Error);
return;
}
ControlInvoker.Invoke(WorkCompleted, this, e);
}
}
}
And here is the usage. One thing to note is that it exposes a static property ControlInvoker that needs to be set only once (you should do it at the beginning of the app load)
Let's take the same example that I posted in question and re write it
DataLoader loader = new DataLoader();
loader.ControlInvoker.Parent = this; // needed to be set only once
private void StartButton_Click(object sender, EventArgs e)
{
Thread thread1 = new Thread(new ThreadStart(PerformWorkerTask));
_worker = new BackgroundWorker();
thread1.Start();
}
public void PerformWorkerTask()
{
loader.WorkDelegate = delegate {
// get any data you want
for (int i = 0; i < 10; i++)
{
Thread.Sleep(100);
}
};
loader.WorkCompleted = delegate
{
// access any control you want
MessageLabel.Text = "Completed";
};
loader.Execute();
}
Cheers