Use case
I'm developing a small application in C# that is called by another application to retrieve data from the Internet. It runs as a process on its own, but almost all of the interaction with it, is managed by the calling application. Therefor it does not have a GUI. However I'd like to add a progress bar using WPF that is shown during certain data retrievals that could take up to a minute. It's fairly easy to make an estimate of how much work is done and how much is left and therefor I find a progress bar suitable.
Research done
I have a fair understanding of threading after reading large parts of Albahari's pdf on threading (http://www.albahari.info/threading/threading.pdf). I have also read through a lot of posts on SO and MSDN in this matter. Most posts suggest the use of a background worker for the time consuming data retrieval while keeping the GUI in the main thread and therefor suggest solutions using a background worker. That feels awkward in this scenario though, where the main task is data retrieval and not GUI interaction.
I've spend a bunch of hours trying to make sense of different tutorials and forum posts while trying to conform them to my problem, but I have not succeeded and now I'm pretty much back to square one. Basically I'd like to end up with the following two classes outlined below:
ProgressBarWindow
public partial class ProgressBarWindow : Window
{
public ProgressBarWindow()
{
InitializeComponent();
}
public void setValue(int value)
{
// This function should be available from the main thread
}
}
Querier
Public class Querier
{
public List<Item> getItems()
{
// call ProgressBarWindow.setValue(0);
...
// call ProgressBarWindow.setValue(100);
// call ProgressBarWindow.Close();
}
}
It's my understanding that UI must run under single threads and therefor my ProgressBarWindow object could not be instantiated in a new thread while at the same time be available to the main thread (kind of).
Dispatcher.BeginInvoke appears to be my savior here but so far I haven't been able to figure out what should go into the Querier class and what to go in the ProgressBarWindow class. How can I make the two threads interact with the same instance of ProgressBarWindow?
Please ask if you need more details and I will try to clarify.
You can use the Progress class to update the UI with the current progress of a long running operation.
First create an instance of Progress in your UI:
Progress<int> progress = new Progress<int>(currentProgress =>
{
progressBar.Value = currentProgress;
//todo do other stuff
});
Then pass it to the long running process:
public List<Item> getItems(IProgress<int> progress)
{
progress.Report(0);
//todo do something
progress.Report(100);
}
Here is a generic function which i generally use:
public static void Invoke(this UIElement element,Action action)
{
element.Dispatcher.Invoke(action, null);
}
And to use it, simply call:
this.Invoke(() => ProgressBarWindow.SetValue(0));
So, in the getItems() function, you would have something along the lines of:
public List<Item> getItems()
{
ProgressBarWindow wnd;
MainWindow.Invoke(() => wnd = new ProgressBarWindow())
MainWindow.Invoke(() => wnd.SetValue(0))
...
MainWindow.Invoke(() => wnd.SetValue(100))
MainWindow.Invoke(() => wnd.Close())
}
Make sure you always have a way to get to the main window is anything (the one running from either App.xml, or App.Run(...). You can then issue any GUI actions through it (even if you have to create a new Loader window for example, as long as it's done within the main thread)
App.xaml
public partial class App : Application
{
private void Application_Startup_1(object sender, StartupEventArgs e)
{
Task.Factory.StartNew<List<int>>(() => Querier.GetItems());
}
}
ProgressBarWindow.xaml.cs
public partial class ProgressWindow : Window
{
public ProgressWindow()
{
InitializeComponent();
Querier.Start +=()=> Visibility = Visibility.Visible;
Querier.Stop += () => Visibility = Visibility.Collapsed;
Querier.ReportProgress +=OnReportProgress;
}
public void OnReportProgress(int value)
{
txtBox.Text = value.ToString();
}
}
ProgressBarWindow.xaml
<Grid>
<TextBox x:Name="txtBox"></TextBox>
</Grid>
Querier
public class Querier
{
public static event Action Start;
public static event Action Stop;
public static event Action<int> ReportProgress;
public static List<int> GetItems()
{
if (Start != null)
App.Current.Dispatcher.BeginInvoke(Start,null);
for (int index = 0; index <= 10; index++)
{
Thread.Sleep(200);
if (ReportProgress != null)
App.Current.Dispatcher.BeginInvoke(ReportProgress, index*10);
}
if (Stop != null)
App.Current.Dispatcher.BeginInvoke(Stop, null);
return Enumerable.Range(1, 100).ToList();
}
}
I am just trying to give an idea hope this will help.
Related
Relatively new to C# and coding in general (first post here). I have a WinForms local application where some information is displayed to the user in a ReadOnly(true) RichTextBox. Almost all my classes need to send information to that RichTextBox. To simplify this process, I created a method inside a static class that uses a locked delegate to send the information to that RichTextBox. Here is a sample:
static class MyClass
{
public delegate void MessageReceivedEventHandler(string message);
public static event MessageReceivedEventHandler messageReceivedEventHandler;
public static void MessageBox(string message)
{
lock (messageReceivedEventHandler)
{
//Thread.Sleep(20);
messageReceivedEventHandler?.Invoke(message);
}
}
}
partial class MyForm : Form
{
public MyForm()
{
MyClass.messageReceivedEventHandler += OnMessageReceived;
}
private void OnMessageReceived(string message)
{
richTextBox1.Text = richTextBox1.Text.Insert(0, $" {message}\n");
}
private void Button1_click()
{
MyClass.MessageBox("This should be working!");
//Add more work here...
}
}
The code above would simply print "This should be working!" inside the RichtTextbox.
The problem is the text from richTextBox1 sometimes becoming empty. This issue seems to appear when the MessageBox method is being called in rapid succession. My assumption was that since I have diffent Tasks running at the same time (in other parts of my code), it probably is two Tasks attempting to use the same static ressource, hence the use of Lock. But I still have the issue.
Adding the Thread.Sleep(20) seems to fix the problem, but that is far from elegant/robust. It starts breaking up again when the time inside Sleep is <10ms.
Edit 1:
To clarify what I mean by "string becoming empty", it means the text from richTextBox1 is == "" at some points, which should not happen since the code is always inserting the text, not replacing it. The OnMessageReceived method is the only place where action is taken on the RichTextBox text.
Edit 2:
I saw many questions related to the other tasks running. First, yes it is a multi-threaded application. The only relation between those tasks and my main form is the "print" function I wrote above. To give more context, this application is used to control the position of stepper motors relative to an electrical signal. When doing so, I need to print important information in my main form. This is why losing the information in my RichTextBox (where I print the information) is an issue. The possible reason of why I am losing the text inside that RichTextBox should be the focus of this thread.
Keep in mind that this is a personnal side project, and not a large scale application.
Thanks,
Laurent
There are multiple problems in your code.
First, you should not lock on a public object, since that allows other threads to lock on the same object, risking interlocking your threads. Second, your symptoms suggest multiple threads are trying to access the ressources. Rather than depending on complex thread locking code, you'd rather schedule UI operations on the UI context, which will allow calling adding message from background tasks.
The best way to do that is to that is by using Control.BeginInvoke()
You can't copy your form instance everywhere, so we'll expose a static method. You could make the class a singleton, but if you need multiple instances that won't work. I'll give a more versatile example. When the static method is called, you don't have access to the form instance anymore, so we'll use IOC pattern with an event and delegate.
Let's make a private static event that all instances will register a callback to in the constructor. When the static method raises the static event, all instances callback will be called. The callback will schedule a modification of its text box.
partial class MyForm : Form
{
private class MessageWriteRequestedEventArgs : EventArgs
{
public string Message { get; }
public MessageWriteRequestedEventArgs(string message)
{
Message = message;
}
}
private static event EventHandler<MessageWriteRequestedEventArgs> MessageWriteRequested;
public MyForm()
{
MessageWriteRequested += OnMessageWriteRequested;
}
public static void WriteMessage(string message)
{
MessageWriteRequested?.Invoke(this, new MessageWriteRequestedEventArgs(message));
}
private void OnMessageWriteRequested(object sender, MessageWriteRequestedEventArgs e)
{
richTextBox1.BeginInvoke(() => WriteMessageSafe(e.message));
}
private void WriteMessageSafe(string message)
{
richTextBox1.Text = richTextBox1.Text.Insert(0, $" {message}\n");
}
private void Button1_click()
{
// you're on ui context, you're safe to access local ui resources
WriteMessageSafe("This should be working!");
// if you have multiple MyForm instances, you need to use the event
WriteMessage("Broadcasting my tralala");
}
}
If you need to write to the textbox from anywhere else :
// do stuff
MyForm.WriteMessage("Ho Ho Ho !");
.NET already includes a class for reporting progress (or any other information) from an asynchronous operation in a thread-safe manner, Progress< T>. It doesn't need locking and even better, it decouples the sender and receiver. Many long-running BCL operations accept an IProgress<T> parameter to report progress.
You haven't explained what's going on in the form, or what task is reporting the data. Assuming the producer is another method in the same form, you could create a Progress<T> instance in the same method that starts the async operation, eg :
async void Button1_Click()
{
var progress=new Progress<string>(ReportMessage);
ReportMessage("Starting");
await Task.Run(()=>SomeLongOp(progress));
ReportMessage("Finished");
}
void SomeLongOp(IProgress<string> progress)
{
for(int i=0;i<1000000;i++)
{
...
progress.Report($"Message {i}");
...
}
}
void ReportMessage(string message)
{
richTextBox1.Text = richTextBox1.Text.Insert(0, $" {message}\n");
}
By using IProgress< T>, the SomeLongOp method isn't tied to a specific form or global instance. It could easily be a method on another class
Publishing lots of messages
Let's say you have a lot of workers, doing a lot of things, eg monitoring a lot of devices, and want all of them to publish messages to the same Log textbox or RTF box. Progress< T> "simply" executes the reporting delegate or event handler on its original sync context. It doesn't have an asynchronous Report method, nor can it queue messages. In a really high-traffic environment, the synchronization switch can delay all workers.
The built-in answer to this is to use one of the pub/sub classes like ActionBlock< T> or a Channel.
An ActionBlock< T> processes the messages in its input queue in order, using a worker task that runs on the ThreadPool by default. This can be changed by specifying a different TaskScheduler in its execution options. By default, its input queue is unbounded.
One could use an ActionBlock to receive messages from multiple workers and display them on a textbox. The block can be created in the constructor, and passed to all workers as an ITargetBlock<T> interface :
ActionBlock<string> _logBlock;
public MyForm()
{
var options=new ExecutionDataFlowBlockOptions {
TaskScheduler=TaskScheduler.FromCurrentSynchronizationContext();
};
_block=new ActionBlock<string>(ReportMessage,options);
}
Now the fun begins. If the workers are created by the form itself, the workers can publish to the block directly :
public async void Start100Workers_Click(...)
{
var workers=Enumerable.Range(0,100)
.Select(id=>DoWork(id,_block));
await Task.WhenAll(workers);
}
async Task DoWork(int id,ITargetBlock<string> logBlock)
{
.....
await logBlock.SendAsync(message);
...
}
Or the block could be exposed through a public property, so other classes/forms in the application can post to it.
public ITargetBlock<string> LogBlock=>_block;
I'm going to show a simple way to do what I think you're after.
I started with a .NET Core 3.1 Win forms application. I added a rich text control to the form. I added a button to the form.
I added a TaskCompletionSource as a instance property - this will be used to control the tasks acting as workers which you described.
CancellationTokenSource sharedCancel = new CancellationTokenSource();
I created an interface to represent something that accepts messages as you described:
public interface IMyMessageSink
{
Task ReceiveMessage(string message);
}
I made my form support this interface.
public partial class Form1 : Form, IMyMessageSink
The ReceiveMessage method looks like this:
public Task ReceiveMessage(string message)
{
if(this.sharedCancel == null || this.sharedCancel.IsCancellationRequested)
return Task.FromResult(0);
this.Invoke(new Action<Form1>((s) => this.richTextBox1.Text = this.richTextBox1.Text.Insert(0, $"{message}\n")), this);
return Task.FromResult(0);
}
You'll see the Invoke handles the synchronization back to the UI thread.
This should probably use BeginInvoke and then convert the APM to async tasks which you can read about here. But for an SO answer the above simple code will suffice.
Also note there's no error handling. You'll want to add that to your generator and to the button handler.
Next I created a class to represent something that creates messages. This class takes the interface created and the cancellation token. It looks like this:
public class MyMessageGenerator
{
CancellationToken cancel;
IMyMessageSink sink;
public MyMessageGenerator(CancellationToken cancel, IMyMessageSink sink)
{
this.cancel = cancel;
this.sink = sink;
}
public async Task GenerateUntilCanceled()
{
try
{
while (!this.cancel.IsCancellationRequested)
{
await sink.ReceiveMessage(this.GetHashCode().ToString());
await Task.Delay(5000, this.cancel);
}
}
catch (OperationCanceledException)
{ }
}
}
In the button handler we create the message generators.
async void button1_Click(object sender, EventArgs e)
{
if (null == this.sharedCancel)
return;
await Task.Run(() => new MyMessageGenerator(this.sharedCancel.Token, this).GenerateUntilCanceled());
}
Finally I added an override for the form closing event:
protected override void OnClosing(CancelEventArgs e)
{
if (null != this.sharedCancel)
{
this.sharedCancel.Cancel();
this.sharedCancel.Dispose();
this.sharedCancel = null;
}
base.OnClosing(e);
}
If the application becomes larger and more complex you would likely benefit by adding services exposed using a DI container. You can read about adding DI to a winforms app here.
I have a UserControl that was built following the MVVM pattern with an exposed function for other apps to send "commands" for the control to do. The commands in this case are strings. I'm trying to find a way to stop the GUI from hanging when a lot of commands are being sent in a short period. Each command should wait for the last one to finish.
Most of these commands do work on a 3rd party map control that is displayed in the main control's view.
The flow goes like this:
App sends command string to control.
Control calls a parse function to parse the string.
After parsing is complete, a certain class is called depending on the command.
Stuff happens i.e. create a model, update ObservableCollection, update the map control, etc.
Here's an example:
The usercontrol:
///The code behind for the control
public partial class MainControl : UserControl
{
public MainControl()
{
InitializeComponent();
}
//Other apps call this function
public void ExecuteCommand(string command)
{
CommandParser.StartParse(command);
}
}
Class to parse the commands:
//Handles parsing a string command and calling the right class
public static class CommandParser
{
public static void StartParse(string command)
{
//parses the command into a string array to hold different parts
DoCommand(parsedCommand);
}
private static void DoCommand(string[] command)
{
switch(command[0])
{
case "addpoint":
AddCommand.AddObj(command);
break;
case "createstyle":
CreateCommand.CreateObj(command);
break;
}
}
}
Two classes that take the parsed command and do something:
//Adds objects to the third party map control
public static class AddCommand
{
public static void AddObj(string[] command)
{
//Adds a point to the third party map control
MapControl.AddPoint(new Point(90, -90)); //just an example
}
}
//Creates model objects to add to observablecollections in viewmodels
public static class CreateCommand
{
public static void CreateObj(string[] command)
{
//create a model
//get the correct viewmodel
viewModel.StylesCollection.Add(styleModel); //StylesCollection is an ObservableCollection
}
}
Very basic example but should show the flow of everything. So imagine getting a a few thousands commands; Creating a model is fast, but because the map control (which is part of the GUI) is being updated every time, or an ObservableCollection (that has a control's itemsource bound to it) is being modified, the GUI hangs when receiving and doing all these commands.
In (the probably unlikely) case that there is a considerable amount of work that can be done off the UI thread, you may implement multi threading. A very basic way of doing this would be as so.
First, create a new thread to run:
var task = new Thread(YourTask);
task.Start();
Then in the thread method where the calculations are done, delegate the result to the UI thread by calling Dispatcher.Invoke. Make sure you don't call Invoke too often (e.g. not more than 10 times per second), as this will again block the UI thread.
public void YourTask()
{
// do calculations and get results
Application.Current.Dispatcher.Invoke(
new Action(() =>
{
// update the UI
}));
}
I've written a WPF WizardFramework which performs some actions in the background using some BackgroundWorker. While processing it can happen that I have to update an ObservableCollection which is bound to my UI.
For this case I've written a ThreadableObservableCollection, which provides threadsafe methods for Insert, Remove and RemoveAt. Though I'm using .NET 4.5 I was not able to get BindingOperations.EnableCollectionSynchronization working without many other invalid access exceptions. My Collection looks like:
public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
private readonly Dispatcher _dispatcher;
public ThreadableObservableCollection()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
public void ThreadsafeInsert(int pos, T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Insert(pos, item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Insert(pos, item);
callback();
});
}
}
[..]
}
This is working as expected, while I am using the wizard in my application. Now I'm using NUnit to write some integrationtests for the application.
There's a listener which waits for the WizardViewModel to finish it's work and looking for some pages which are injected in the Steps-Collection. After the asyncrone work is done I can use Validate to check the viewmodel state.
Unfortunately I'm using a ManualResetEvent to wait for the wizard to close. This looks like following:
public class WizardValidator : IValidator, IDisposable
{
private WizardViewModel _dialog;
private readonly ManualResetEvent _dialogClosed = new ManualResetEvent(false);
[..]
public void ListenTo(WizardViewModel dialog)
{
_dialog = dialog;
dialog.RequestClose += (sender, args) => _dialogClosed.Set();
dialog.StepsDefaultView.CurrentChanged += StepsDefaultViewOnCurrentChanged;
_dialogClosed.WaitOne();
}
[..]
}
Now there's a problem:
While the Application is running the UI Thread is not blocked, the Collection can be updated without any problems. But in my testcases the "main" Thread where I initialize the ViewModel (and because of that the Collections) is an AppDomainThread which is blocked by the testcode. Now my ThreadsafeInsert wants to update the collection but cannot use the AppDomain Thread.
But I have to wait for the wizard to finish, how can I solve this kind of deadlock? Or is there a more elegant solution for this one?
edit:
I worked around this problem with a check if there's a user interface, and only then I invoke on the Application-Thread, otherwise I change the collection intentionally on another thread. This does not prevent the exception, but it is not recognized from the test... the items are inserted nevertheless, only the NotifyCollectionChanged-Handler is not called (which is only used in the UI anyway).
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
{
Steps.Insert(pos, step);
stepsView.MoveCurrentTo(step);
});
}
else
{
new Action(() => Steps.Insert(pos, step)).BeginInvoke(ar => stepsView.MoveCurrentToPosition(pos), null);
}
This is an ugly workaround and I am still interested in a clean solution.
Is there a way to use an alternate Dispatcher to create (e.g.) the whole ViewModel and use this to change my collection?
As I see the main problem that main thread is blocked and other operations are trying to be executed in main thread too? What about not to block main thread, like this:
// helper functions
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
// in your code:
while(!_dialogClosed.WaitOne(200))
DoEvents();
If it will not help then I guess need to try some SynchronisationContext workarounds.
I think the problems boil down to the fact that you create ObservableCollection that is tied to Dispatcher object.
Involving Dispatcher object directly is almost never good idea(as you just witnessed). Instead I would suggest you to see how others have implemented ThreadSafeObservableCollection. This is a little example I put together, it should illustrate the point:
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lock = new object();
public ThreadSafeObservableCollection()
{
BindingOperations.CollectionRegistering += CollectionRegistering;
}
protected override void InsertItem(int index, T item)
{
lock (_lock)
{
base.InsertItem(index, item);
}
}
private void CollectionRegistering(object sender, CollectionRegisteringEventArgs e)
{
if (e.Collection == this)
BindingOperations.EnableCollectionSynchronization(this, _lock);
}
}
I'm writing a simple Windows forms application to get me into the swing of things with Threads. So far what I have is working, but what I would like to do is contain it all in a seperate class rather than directly in my forms code.
I have a background thread that starts and retrieves data from a database. I then display that data in to a listbox.
private delegate void UpdateListValues(List<ListBoxItem> itemList);
private void form_main_Shown(object sender, EventArgs e)
{
// Set the loading text.
list_selection.Items.Add(ListHelpers.LoadingItem());
// Start the data access on a seperate thread.
Thread worker = new Thread(GetInvoicingData);
worker.IsBackground = true;
worker.Start();
}
private void GetInvoicingData()
{
// Query database
List<ListBoxItem> values = DAC.GetInvoicingAccounts();
// Display results
BeginInvoke(new UpdateListValues(DisplayList), new object[] { values });
}
private void DisplayList(List<ListBoxItem> itemList)
{
// Display each result
list_selection.Items.Clear();
for (int i = 0; i < itemList.Count; i++)
{
list_selection.Items.Add(itemList[i]);
}
}
The problem is that in the DisplayList method, I won't be able to access the list box (list_selection) because it's part of the form class. Does anyone have any suggestions on how I can do this.
Also, I'm new to threading so feel free to tell me I'm doing it absolutely wrong. I just used the example from http://www.codeproject.com/Articles/23517/How-to-Properly-Handle-Cross-thread-Events-and-Upd to get me to where I am now.
Thanks
How about something like this:
// Added the form's class declaration to highlight separation of thread code into a separate class, but may not be exactly the same as yours depending on naming
public class Form1 : Form
{
private readonly DataRetriever _dataRetriever;
private void form_main_Shown(object sender, EventArgs e)
{
// Set the loading text.
list_selection.Items.Add(ListHelpers.LoadingItem());
// Create the DataRetriever, and provide it with a delegate to DisplayList for returning data
_dataRetriever = new DataRetriever(DisplayList);
// Start retrieving data on a separate thread...
_dataRetriever.GetData();
}
private void DisplayList(List<ListBoxItem> itemList)
{
if (InvokeRequired)
{
// Ensure the update occurs on the UI thread
Invoke((Action)(() => DisplayList(itemList)));
return;
}
// Display each result
list_selection.Items.Clear();
foreach (var item in itemList)
{
list_selection.Items.Add(item);
}
}
}
// Separate class to hold thread code
public class DataRetriever
{
public delegate void UpdateCallbackDelegate(List<ListBoxItem> itemList);
private readonly UpdateCallbackDelegate _updateCallback;
public DataRetriever(UpdateCallbackDelegate updateCallback)
{
_updateCallback = updateCallback;
}
public void GetData()
{
var thread = new Thread(GetInvoicingData)
{
IsBackground = true
};
thread.Start();
}
private void GetInvoicingData()
{
// Not sure whether "DAC" is a static class, if it needs to be constructed
// in the DataRetriever's constructor, or passed to it as a parameter
_updateCallback(DAC.GetInvoicingAccounts());
}
}
As you can see, all the thread code is now in a separate class DataRetriever, and a delegate provided when constructing it to enable the retrieved data to be passed back to the form once the retrieval is complete. The method that handles the callback ensures that the call is marshalled to the UI thread to prevent cross-thread exceptions.
I would like to point out that this is not presented as the "best" way to do this, but merely as an answer to the question (how to separating threading code into a separate class). As others have mentioned, there are already mechanisms in place to do this sort of thing (e.g. BackgroundWorker). Some complexity has been omitted for clarity. For example, in the implementation presented here, if you were to call GetData() multiple times (with each call occurring before the previous ones have returned their data), you would have multiple queries occurring simultaneously, and as they are running asynchronously, may return their data in an arbitrary order. This may or may not be an issue in your case.
I have a layered worker class that I'm trying to get progress reports from. What I have looks something like this:
public class Form1
{
private void Start_Click()
{
Controller controller = new Controller();
controller.RunProcess();
}
}
public class Controller
{
public void RunProcess()
{
Thread newThread = new Thread(new ThreadStart(DoEverything));
newThread.Start();
}
private void DoEverything()
{
// Commencing operation...
Class1 class1 = new Class1();
class1.DoStuff();
Class2 class2 = new Class2();
class2.DoMoreStuff();
}
}
public class Class1
{
public void DoStuff()
{
// Doing stuff
Thread.Sleep(1000);
// Want to report progress here
}
}
public class Class2
{
public void DoMoreStuff()
{
// Doing more stuff
Thread.Sleep(2000);
// Want to report progress here as well
}
}
I've used the BackgroundWorker class before, but I think I need something a bit more free form for something like this. I think I could use a delegate/event solution, but I'm not sure how to apply it here. Let's say I've got a few labels or something on Form1 that I want to be able to update with class1 and class2's progress, what's the best way to do that?
Using events is the most straightforward solution. When you subscribe to the event from the main thread, the handler should check the Control.IsInvokeRequired to know whether it must call itself again through Invoke(...) to get the message passed to the right thread.
John is correct. You want to utilize events and for that you'll need to use a delegate or delegates. This might give you some ideas.
http://www.yoda.arachsys.com/csharp/threads/winforms.shtml
If you do not want to block the processing threads during notification, you can use Control.BeginInvoke() for fire & forget behavior.
To decrease the number of calls and update progress on a regular interval, you may want to encapsulate the states of different operations in classes.
This way you can just write states to e.g. volatile fields - of presumably another, aggregate-state class - and use a timer on the GUI thread to re-read state and refresh labels accordingly.