First you need to use Dispatcher.Invoke
to change the UI from another thread and to do that from another class, you can use events.
Then you can register to that event(s) in the main class and Dispatch the changes to the UI and in the calculation class you throw the event when you want to notify the UI:
class MainWindow : Window
{
private void startCalc()
{
//your code
CalcClass calc = new CalcClass();
calc.ProgressUpdate += (s, e) => {
Dispatcher.Invoke((Action)delegate() { /* update UI */ });
};
Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod));
calcthread.Start(input);
}
}
class CalcClass
{
public event EventHandler ProgressUpdate;
public void testMethod(object input)
{
//part 1
if(ProgressUpdate != null)
ProgressUpdate(this, new YourEventArgs(status));
//part 2
}
}
UPDATE:
As it seems this is still an often visited question and answer I want to update this answer with how I would do it now (with .NET 4.5) - this is a little longer as I will show some different possibilities:
class MainWindow : Window
{
Task calcTask = null;
void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1
async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2
{
await CalcAsync(); // #2
}
void StartCalc()
{
var calc = PrepareCalc();
calcTask = Task.Run(() => calc.TestMethod(input)); // #3
}
Task CalcAsync()
{
var calc = PrepareCalc();
return Task.Run(() => calc.TestMethod(input)); // #4
}
CalcClass PrepareCalc()
{
//your code
var calc = new CalcClass();
calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate()
{
// update UI
});
return calc;
}
}
class CalcClass
{
public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5
public TestMethod(InputValues input)
{
//part 1
ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus
//part 2
}
}
static class EventExtensions
{
public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent,
object sender, T args)
{
if (theEvent != null)
theEvent(sender, new EventArgs<T>(args));
}
}
@1) How to start the "synchronous" calculations and run them in the background
@2) How to start it "asynchronous" and "await it": Here the calculation is executed and completed before the method returns, but because of the async
/await
the UI is not blocked (BTW: such event handlers are the only valid usages of async void
as the event handler must return void
- use async Task
in all other cases)
@3) Instead of a new Thread
we now use a Task
. To later be able to check its (successfull) completion we save it in the global calcTask
member. In the background this also starts a new thread and runs the action there, but it is much easier to handle and has some other benefits.
@4) Here we also start the action, but this time we return the task, so the "async event handler" can "await it". We could also create async Task CalcAsync()
and then await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);
(FYI: the ConfigureAwait(false)
is to avoid deadlocks, you should read up on this if you use async
/await
as it would be to much to explain here) which would result in the same workflow, but as the Task.Run
is the only "awaitable operation" and is the last one we can simply return the task and save one context switch, which saves some execution time.
@5) Here I now use a "strongly typed generic event" so we can pass and receive our "status object" easily
@6) Here I use the extension defined below, which (aside from ease of use) solve the possible race condition in the old example. There it could have happened that the event got null
after the if
-check, but before the call if the event handler was removed in another thread at just that moment. This can't happen here, as the extensions gets a "copy" of the event delegate and in the same situation the handler is still registered inside the Raise
method.