I have a generic WPF Window, it uses the MVVM pattern. So, once it's declared, it receives a ViewModel object. The objective of this Window is to be shown to the user while long operations run in an Autocad API enviroment. All my code is inside a dll file.
How can I pass a method from other class to be run inside the StartTask method?
Must be a generic way, because many methods could be called from inside this Window. Could be done inside the ViewModel object.
public partial class BusyIndicatorView : Window
{
internal BusyIndicatorViewModel BusyIndicatorViewModel { get; set; }
public BusyIndicatorView(BusyIndicatorViewModel busyIndicatorViewModel)
{
InitializeComponent();
BusyIndicatorViewModel = busyIndicatorViewModel;
StartTask();
}
private async void StartTask()
{
try
{
while (true)
{
// I gotta run here the generic method
}
Close();
}
catch (Exception ex)
{
MessageBox.Show("Error");
}
}
}
The async method StartTask, inside the Window, is the way I have found to make the interface responsive while runnning heavy work.
Declaring the Window and the task, as the following way, doesn't fit my needs, because the Window is not responsive. Autocad API does not support multithread.
var heavyTask = new HeavyTask();
var window = new Window();
heavyTask.Start();
window.Show();
I'm expecting to have a window, that is showed while a long operation is running. The window shows the progress and is responsive, allowing the user move it, and click buttons.
You should never run any long-running and/or async code from a constructor. This is a code smell as type construction is supposed to return fast in the same manner a field or property does not silently run long-running operations.
The other problem is that constructors can't be async. To solve this problem, you must let the owner of the type explicitly start the async operation or long-running initialization e.g. by providing a Initialize
or InitializeAsync
method. This way the method can have a correct async Task
signature (instead of async void
).
In your case you could make StartTask
public and let it accept a delegate as argument.
The following example adds a ShowAsync
method to the API of BusyIndicatorView
that enables the caller to pass in an async delegate:
Usage example
private async Task ShowBusyIndicator()
{
var busyIndicatorViewModel = new BusyIndicatorViewModel();
var busyIndicator = new BusyIndicatorView(busyIndicatorViewModel);
// Start a non-cancellable opertaion
await busyIndicator.ShowAsync(DoSomethingAsync);
var cancellationTokeSource = new CancellationTokenSource();
// Start a cancellable operation
try
{
await busyIndicator.ShowAsync(DoSomethingCancellableAsync, cancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
// TODO::Perform clean up if necessary
}
}
private async Task DoSomethingAsync()
{}
private async Task DoSomethingCancellableAsync(CancellationToken cancellationToken)
{}
BusyIndicatorView.xaml.cs
public partial class BusyIndicatorView : Window
{
private Func<CancellationToken, Task> cancellableAsyncOperation;
internal BusyIndicatorViewModel BusyIndicatorViewModel { get; }
public BusyIndicatorView(BusyIndicatorViewModel busyIndicatorViewModel)
{
InitializeComponent();
BusyIndicatorViewModel = busyIndicatorViewModel;
//StartTask();
}
public async Task ShowAsync(Func<Task> asyncOperation)
=> ShowAsync((token) => asyncOperation.Invoke(), CancellationToken.None);
public async Task ShowAsync(Func<CancellationToken, Task> cancellableAsyncOperation, CancellationToken cancellationToken)
{
this.cancellableAsyncOperation = cancellableAsyncOperation;
Show();
await StartAsync(cancellationToken);
}
private async Task StartAsync(CancellationToken cancellationToken)
{
try
{
await this.cancellableAsyncOperation.Invoke(cancellationToken);
Close();
}
catch (OperationCanceledException)
{
// TODO::Perform clean up if necessary
throw;
}
catch (Exception ex) // <-- Code smell
{
//MessageBox.Show("Error");
MessageBox.Show(ex.Message); // At least show the message
}
}
}