How do I show a modal dialog that tracks a task? - c#

It's clear to me if I have a button that triggers an event, but in the case below, I want to pop up a dialog. The code below is a mess, I don't know how to do this right. I think async/await is part of this, but I'm not clear on this case.
class TaskObject : Form
{
public void MyFunc()
{
MyDialog d = new MyDialog(this);
d.ShowDialog(); // I don't want any other interaction except this dialog's controls
}
internal async Task<bool> LongFunction()
{
// ...
return true;
}
}
class MyDialog : Form
{
Task<bool> task;
public async MyDialog(TaskObject o)
{
task = new Task<bool>(o.LongFunction);
await task;
}
void when_LongFunction_does_something_interesting()
{
this.MyTextBox.Text = "Something interesting";
}
void when_task_completes()
{
this.CancelButton.Visible = false;
this.CloseButton.Visible = true;
}
}

There are two points here:
The constructor of your form cannot have the async modifier. As an alternative, you can use the Load event instead.
(Optional) You don't need to pass an instance of the "parent" form to the constructor, you can get it directly from the Owner property if you use ShowDialog(this) instead of ShowDialog().
Also, remember to dispose of any dialog form after you're done with it. Preferably, wrap the usage of it within a using block.
Here's how I would do it; In the TaskObject form:
internal async Task<bool> LongFunction()
{
// Do some magic.
// await ...
return true;
}
public void MyFunc()
{
using (MyDialog d = new MyDialog())
{
d.ShowDialog(this);
}
}
In the MyDialog form:
private async void MyDialog_Load(object sender, EventArgs e)
{
TaskObject owner = this.Owner as TaskObject;
await owner.LongFunction();
when_task_completes();
}
If you also want to track the progress of LongFunction, you can add a Progress<T> parameter to it and use it like this:
internal async Task<bool> LongFunction(IProgress<string> progress)
{
// Do some magic.
progress.Report("Something interesting");
// await ...
// More magic.
return true;
}
Then you can do something like this:
private async void MyDialog_Load(object sender, EventArgs e)
{
TaskObject owner = this.Owner as TaskObject;
var progress = new Progress<string>(s => when_LongFunction_does_something_interesting(s));
await owner.LongFunction(progress);
when_task_completes();
}
void when_LongFunction_does_something_interesting(string message)
{
this.MyTextBox.Text = message;
}
Note that I used Progress<string> as an example. Instead of string, you can use whatever type works best for your situation.

Related

How to Acces a Property of an Usercontrol inside a Thread (Public Static void)

How to fix this error:
Error: Keyword 'this' is not valid in a static property, static method, or static field initializer
Here is the faulty code:
private void proxietype_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Dispatcher.BeginInvoke(((Action)delegate
{
Thread t = new Thread(new ThreadStart(() => Check()));
t.Start();
}));
}
public static void Check()
{
((UserControlVPN)Window.GetWindow(this)).notification.IsActive = true;
main.notification.IsActive = true;
main.notification.Message.Content = "Please wait";
}
Here as a Picture :
1) You don't need a Dispatcher to run the Thread or Task asynchonously
2) Use Task instead of Thread
3) You may access to instance via static field assigned e.g. in constructor
4) Also you may get an exception while accessing Window properties from other than Main UI Thread. Here you'll need a Dispatcher.
private void proxietype_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Task.Run(() => Check());
}
// define static field
private static UserControlVPN _thisWindow;
public UserControlVPN()
{
// assign instance to the field in constructor
_thisWindow = (UserControlVPN)Window.GetWindow(this);
}
public static void Check()
{
Dispatcher.BeginInvoke((Action)() =>
{
_thisWindow.notification.IsActive = true;
_thisWindow.notification.Message.Content = "Please wait";
});
//...
}
Or you may just remove static. Bacause with this implementation you may have only one instance of the UserControl with no problems.
5) Better to use async/await approach and modify the Window's properties outside of Task.
private async void proxietype_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
proxietype.Enabled = false;
main.notification.IsActive = true;
main.notification.Message.Content = "Please wait";
await Task.Run(() => Check());
main.notification.Message.Content = "";
main.notification.IsActive = false;
proxietype.Enabled = true;
}
public static void Check()
{
// do job...
}
Be sure that you have no unhandled exceptions possible inside of Check().

What is the best patter to call async methods after app start? (Fire&Forget in Constructor?)

In my application I want to setup my ViewModels by queried data (from async methods) right after my application started.
In my case I have a .NET Framework WPF application and after the start I want to begin loading the data by async methods. After awaiting the data I want to update my view models.
What is the best way and the best entry point to do this?
My first approach is the following:
public App()
{
// Do synchronous initializing stuff
// Load data and initialize by viewmodel with it
#pragma warning disable CS4014
ProgramStateViewModel.Instance.LoadDataAndSetupAsync();
#pragma warning restore CS4014
}
The problem with this is I got the compiler warning and I have to handle it by suppressing it.
Actually I want a Fire-and-Forget call, but the method I am calling is async and returns a Task.
How to do it better?
You can attach an async Startup event handler:
public App()
{
Startup += async (s, e) =>
{
await ProgramStateViewModel.Instance.LoadDataAndSetupAsync();
};
}
Try the following, using discards:
public App()
{
// Do synchronous initializing stuff
// Load data and initialize by viewmodel with it
_ = ProgramStateViewModel.Instance.LoadDataAndSetupAsync();
}
By using discards, you can return the Task and you don't declare anything else that will not be used.
Be aware that even without the warning the dangers still apply.
Especially if you have an error in your background work which well not be caught. To avoid this situation, please check another SO question here on managing exceptions on the separate thread.
A possibility is to use the OnStartup function in the App.xaml.cs file. For the asynchronous call, various methods exist, following some methods to make an asynchronous call.
Using BackgroundWorker
public partial class App : Application
{
protected override void OnStartup(System.Windows.StartupEventArgs e)
{
BackgroundWorker _backgroundWorker;
if (_backgroundWorker != null)
{
_backgroundWorker.CancelAsync();
}
_backgroundWorker = CreateBackgroundWorker();
_backgroundWorker.RunWorkerAsync();
}
}
private BackgroundWorker CreateBackgroundWorker()
{
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += _backgroundWorker_DoWork;
bw.ProgressChanged += _backgroundWorker_ProgressChanged;
bw.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
return bw;
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
}
private void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
Using Thread
Method 1
myThread = new Thread(() => CustomTaskAsync());
myThread.Start();
private void CustomTaskAsync()
{
...............
}
Method 2
Thread thread = new Thread(new ThreadStart(CustomTaskAsync));
thread.Start();
public void CustomTaskAsync()
{
..................
}
Method 3
Thread thread = new Thread(CustomTaskAsync);
thread.Start();
public void CustomTaskAsync()
{
...............
}
Using Task
Method 1
bool result ;
Task<bool> task = Task.Run<bool>(async () => await CustomTaskAsync());
str = task.Result;
public async Task<bool> CustomTaskAsync()
{
bool result;
result= await Task.Factory.StartNew(() => CustomTask());
return result;
}
private bool CustomTask()
{
bool result;
return result;
}
Method 2
CustomTaskAsync().Wait();
public async Task CustomTaskAsync()
{
await Task.Run(() => {
.................
});
}

WPF MVVM Async event invoke

I am lost in this one, i want my Viewmodel to use a event delegate so i can subscribe to it, open some dialog and wait for the dialog result. Later the ViewModel should do whatever it wants with the dialog result.
Here is how i implemented it (resumed code):
public class MyViewModel()
{
public delegate TributaryDocument SearchDocumentEventHandler();
public event SearchDocumentEventHandler SearchDocument;
//Command for the search button
public CommandRelay SearchDocumentCommand { get; set; }
//Document that i found in the dialog.
public TributaryDocument Document { get; set; }
public MyViewModel()
{
SearchDocumentCommand = new CommandRelay(DoSearchDocument);
}
//The command execution
public void DoSearchDocument()
{
//Event used here !
Document = SearchDocument?.Invoke();
}
}
public class MyUIControl : UserControl
{
public MainWindow MainWindow { get; }
public MyUIControl()
{
MainWindow = Application.Current.Windows[0] as MainWindow;
DataContextChanged += MyUIControl_DataContextChanged;
}
private void MyUIControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var modelView = (MyViewModel)DataContext;
modelView.SearchDocument += MyUIControl_SearchDocument;
}
private TributaryDocument MyUIControl_SearchDocument()
{
//Dont know what to do here... i am lost on this part.
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
}
//The signature for MainWindow.ShowDialog
public async Task<object> ShowDialog(object dialog)
{
return await DialogHost.Show(dialog, "MainDialog");
}
MyDocumentSearcherDialog is just a dialog where i search and return a TributaryDocument object.
The problem to my understanding comes from this part (since i cant compile it):
private TributaryDocument MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
I cant use await without changing the method signature to async. If i change it to async then i must return a Task<TributaryDocument> and change the event delegate:
public delegate Task<TributaryDocument> SearchDocumentEventHandler();
//On MyUIControl
private Task<TributaryDocument> MyUIControl_SearchDocument()
{
return await MainWindow.ShowDialog(new MyDocumentSearcherDialog());
}
//On MyViewModel
public async void DoSearchDocument()
{
//Event used here !
Document = await Task.Run(async () => await SearchDocument?.Invoke());
}
If i do this i get the following exception:
Additional information: The calling thread must be STA, because many
UI components require this.
It seems like all you need to do is to remove the Task.Run (there is no need to Offload to another thread in this situation). The Task.Run will definitely give you a STA Thread Exception if you are doing UI work from within.
However, in short the Async and Await Pattern will create a continuation with the current SynchronisationContext, so there is no need to worry about it.
public async void DoSearchDocument()
{
await SearchDocument?.Invoke();
}
Note : Since this is an event, it's about the only place it's OK to use async void.

How to display WatiForm while executing method Async-Await

In my c# winforms application, while displaying the form I am loading the data in LoadDataAsync method, but before loading the data I want to start displaying the Splash Screen, which is not happening can someone guide me what I am doing wrong...or any ideas.
public partial class DepartmentListDetailView : BaseForm
{
private DepartmentDataScope _departmentDataScope;
private AppConfigDataScope _appConfigDataScope;
private DepartmentSearch _departmentSearch;
public DepartmentListDetailView() : base()
{
InitializeComponent();
Init();
}
private async void Init()
{
_departmentDataScope = new DepartmentDataScope();
_appConfigDataScope = new AppConfigDataScope();
_departmentSearch = new DepartmentSearch();
var res = await LoadDataAsync();
}
private async Task<bool> LoadDataAsync()
{
Ssm.ShowWaitForm(); // before loading the data, I want to display the spalsh screen
var result = await _departmentDataScope.FetchDataAsync();
BbiSearch.Enabled = true;
if (Ssm.IsSplashFormVisible)
{
this.Invoke((MethodInvoker) Ssm.CloseWaitForm);
}
return true;
}
}
Thanks
Show it before calling the async method like below code.
Ssm.ShowWaitForm();
var res = await LoadDataAsync();
if (Ssm.IsSplashFormVisible)
{
this.Invoke((MethodInvoker) Ssm.CloseWaitForm);
}

Using an event wait handle to block for window closed, then wrapping as async-await. Acceptable?

I have a window displaying service, with a CloseWindow method that is called by the view. I want to create a blocking method in my calling code. So I can block while a window pops up and to allow outputs to come back from the window.
Is this use of Manual Reset acceptable? Are there any technical or design problems with it or with the way I mix it with TPL?
Here is that service
private readonly ManualResetEvent closedEvent = new ManualResetEvent(true);
public void DisplayWindow(){
window = new MyWindow();
}
public void CloseWindow() {
window.Close();
closedEvent.Set();
}
//new
public async Task WaitClosed()
{
await Task.Run(() => this.closedEvent.WaitOne());
}
here is some code that calls it.
public void DisplayWindow(string content, string title)
{
dialogservice.DisplayWindow();
}
public async Task DisplayWindowAsync(string content, string title)
{
dialogservice.DisplayWindow();
await dialogservice.WaitClosed();
}
It looks like it could be done more simply and without the hung thread:
private readonly TaskCompletionSource<bool> windowClosed
= new TaskCompletionSource<bool>();
public Task WindowClosed { get { return windowClosed.Task; } }
public void CloseWindow() {
window.Close();
windowClosed.TrySetResult(true);
}
with:
await dialogservice.WindowClosed;

Categories

Resources