async Task Main() in WinForms application having async initialization - winforms

We're having a winforms application that uses an async initialization process. Simplified you can say that the application will run the following steps:
Init - this runs async
Show MainForm
Application.Run()
The currently existing and working code looks like this:
[STAThread]
private static void Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
var task = StartUp();
HandleException(task);
Application.Run();
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
private static async void HandleException(Task task)
{
try
{
await Task.Yield();
await task;
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
The background how this is working is described very detailed by Mark Sowul here.
Since C# 7.1 we're able to use async Task in main method. We tried it in a straight forward way:
[STAThread]
private static async Task Main()
{
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
try
{
await StartUp();
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
Application.ExitThread();
}
}
private static async Task StartUp()
{
await InitAsync();
var frm = new Form();
frm.Closed += (_, __) => Application.ExitThread();
frm.Show();
}
private static async Task InitAsync()
{
// the real content doesn't matter
await Task.Delay(1000);
}
But that doesn't work. The reason is clear. All the code after the first await will be forwarded to the message loop. But the message loop hasn't startet yet because the code that starts it (Application.Run()) is located after the first await.
Removing the synchronization context will fix the problem but causes to run the code after await in a different thread.
Reordering the code to call Application.Run() before the first await will not work because it is a blocking call.
We try to use the new feature of having an async Task Main() that allows us to remove the HandleException-solution that is hard to understand. But we don't know how.
Do you have any suggestions?

You don't need async Main. Here is how it can possibly be done:
[STAThread]
static void Main()
{
void threadExceptionHandler(object s, System.Threading.ThreadExceptionEventArgs e)
{
Console.WriteLine(e);
Application.ExitThread();
}
async void startupHandler(object s, EventArgs e)
{
// WindowsFormsSynchronizationContext is already set here
Application.Idle -= startupHandler;
try
{
await StartUp();
}
catch (Exception)
{
// handle if desired, otherwise threadExceptionHandler will handle it
throw;
}
};
Application.ThreadException += threadExceptionHandler;
Application.Idle += startupHandler;
try
{
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Application.Idle -= startupHandler;
Application.ThreadException -= threadExceptionHandler;
}
}
Note, if you don't register threadExceptionHandler event handler and StartUp throws (or anything else on the message loop throws, for the matter), it will still work. The exception will be caught inside the try/catch which wraps Application.Run. It will just be a TargetInvocationException exception with the original exception available via its InnerException property.
Updated to address the comments:
But for me it looks very strange to register an EventHandler to the
idle event so startup the whole application. It's totally clear how
that works but still strange. In that case I prefer the
HandleException solution that I already have.
I guess it's a matter of taste. I don't know why WinForms API designers didn't provide something like WPF's Application.Startup. However, in the lack of a dedicated event for this on WinForm's Application class, deferring specific initialization code upon the first Idle event is IMO an elegant solution, and it's widely used here on SO.
I particularly don't like the explicit manual provisioning of WindowsFormsSynchronizationContext before Application.Run has started, but if you want an alternative solution, here you go:
[STAThread]
static void Main()
{
async void startupHandler(object s)
{
try
{
await StartUp();
}
catch (Exception ex)
{
// handle here if desired,
// otherwise it be asynchronously propogated to
// the try/catch wrapping Application.Run
throw;
}
};
// don't dispatch exceptions to Application.ThreadException
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
using (var ctx = new WindowsFormsSynchronizationContext())
{
System.Threading.SynchronizationContext.SetSynchronizationContext(ctx);
try
{
ctx.Post(startupHandler, null);
Application.Run();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
System.Threading.SynchronizationContext.SetSynchronizationContext(null);
}
}
}
IMO, either approach is more clean than the one used in your question. On a side note, you should be using ApplicationContext to handle the form closure. You can pass an instance of ApplicationContext to Application.Run.
The only point that I'm
missing is your hint that the synchronization context is already set.
Yes it is - but why?
It is indeed set as a part of Application.Run, if not already present on the current thread. If you like to learn more details, you could investigate it in .NET Reference Source.

Related

Due to the use of Dispatcher,t he window still locks

I have a program similar to chatbot in Wpf.
I have a stack where I create the user controls I have and enter them.
I have to use Net3.5 .
The response from the server is delayed.
The problem I have is when I type and send the textbox the server does not answer,
I can not type another question and the window is locked.
Did I use Dispatcher correctly?
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
DispatchFit();
}
private void DispatchFit()
{
Dispatcher.BeginInvoke(new Action(ResponsServer), DispatcherPriority.Background);
}
public void ResponsServer()
{
Thread.Sleep(3000);
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}
When that ResponsServer() callback is being processed on your UI thread, then that Sleep is elongating the amount of time that callback is taking to process (Sleep does not pump the UI's dispatcher message queue).
If you want your callback to be done after 3 seconds, then you need to use a timer, or you can use "async" to cause a delayed processing of your callback.
Look at this question: Delayed Dispatch Invoke?
Or use this to have a BackgroundWorker do the delay and then call your ResponsServer on the UI thread (not the best code as it creates a new BackgroundWorker each time).
https://www.codeproject.com/Tips/240274/Execute-later-for-delayed-action
You are a little confused about the methods.
If I understand correctly, then Sleep is an emulation of the delay in the execution of sending a message to the server.
Then you need something like:
private async void send_ClickAsync(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
await ResponsServerAsync();
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}
public async Task ResponsServerAsync()
{
Thread.Sleep(3000);
}
For .Net Framework 3.5
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
Thread thread = new Thread(ResponsServer);
thread.Start();
}
public void ResponsServer()
{
Thread.Sleep(3000);
if (Dispatcher.CheckAccess())
{
StackChildrenAdd();
}
else
{
Dispatcher.InvokeAsync(StackChildrenAdd);
}
}
private void StackChildrenAdd()
{
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}

Global Error Handling in Task.Run for realtime User ntofication

I have a wpf c# app.
I normally use a global error handler to catch all errors:
private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
try
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => Xceed.Wpf.Toolkit.MessageBox.Show(e.Exception.ToString(), "Error",
MessageBoxButton.OK, MessageBoxImage.Error)));
e.Handled = true;
InformedWorkerDataService.Common.Shared.RecordMessage(e.Exception.ToString(), true);
}
finally { }
}
However, if start off a task.run 'bit of code' and it throws an error then i have observed that error is not caught:
Task.Run(() =>
{
throw and error here
});
so I have to put a 'Try-Catch' thing in to capture it:
Task.Run(() =>
{
try
{
throw an error here
}
catch (Exception ex)
{
do something with error
}
});
~ which defeats the object of having a Global Error handler
But, if i use this approach:
TaskScheduler.UnobservedTaskException += (s, e) => {
e.Exception //The Exception that went unobserved.
e.SetObserved(); //Marks the Exception as "observed," thus preventing it from triggering exception escalation policy which, by default, terminates the process.
};
... it will do my global exception handling but if I want to notify the user of the error real-time it does not do so very well because IT IS on a separate thread.
What would be a good compromise?
Unfortunately the TaskScheduler.UnobservedTaskException is not guarenteed to fire in real time with the exception being thrown. This means that using this handler for user notification can be quite confusing as the user action and error notification will not happen synchronously. For user driven handling of 'unexpected' task exceptions, you could create helper methods as below and use TaskEx.Run instead of Task.Run:
public static class TaskEx
{
public static Task Run(Action function)
{
return Task.Run(() =>
{
try
{
function();
}
catch (Exception ex)
{
TraceEx.TraceException(ex);
//Dispatch your MessageBox etc.
}
});
}
}
Obviously this is not as simple as adding a global handler (which should still be done for tracing purposes) but is simple enough to implement in UI driven code.

Winforms recursive file scan blocks UI

I have a program that searches the given directory and adds all the files to a list view. My problem is that the ui thread gets stuck while the search is busy. I have tried using tasks but can’t get it to work in async. The list view must be updated after each file has been found.
I have done a lot of reading about the TPL and how to use it but can’t get it to work in this case. I got it to work where the processing of data is in one method that create a task to process it. Can any one tel me what is wrong in the code below and how to fix it?
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
WalkDirectory(new DirectoryInfo(drive));
});
}
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
public bool WalkDirectory(DirectoryInfo directory)
{
if (directory == null)
{
throw new ArgumentNullException("directory");
}
return this.WalkDirectories(directory);
}
private bool WalkDirectories(DirectoryInfo directory)
{
bool continueScan = true;
continueScan = WalkFilesInDirectory(directory);
if (continueScan)
{
DirectoryInfo[] subDirectories = directory.GetDirectories();
foreach (DirectoryInfo subDirectory in subDirectories)
{
try
{
if ((subDirectory.Attributes & FileAttributes.ReparsePoint) != 0)
{
continue;
}
if (!(continueScan = WalkDirectory(subDirectory)))
{
break;
}
}
catch (UnauthorizedAccessException)
{
continue;
}
}
}
if (continueScan)
{
testTaskUpdateLabel(directory.FullName);
}
return continueScan;
}
private bool WalkFilesInDirectory(DirectoryInfo directory)
{
bool continueScan = true;
// Break up the search pattern in separate patterns
string[] searchPatterns = _searchPattern.Split(';');
// Try to find files for each search pattern
foreach (string searchPattern in searchPatterns)
{
if (!continueScan)
{
break;
}
// Scan all files in the current path
foreach (FileInfo file in directory.GetFiles(searchPattern))
{
try
{
testTaskUpdate(file.FullName);
}
catch (UnauthorizedAccessException)
{
continue;
}
}
}
return continueScan;
}
If you use a BackgroundWorker class, the UI will work and progress can be updated in the ProgressChanged event handler.
MSDN Reference
Can any one tel me what is wrong in the code below and how to fix it?
The problem is here
public void testTaskUpdateLabel(string labelTeks)
{
Task taskUpdateLabel = new Task(() =>
{
label4.Text = labelTeks;
});
taskUpdateLabel.Start(uiScheduler);
}
You should not use TPL to update the UI. TPL tasks are for doing non UI work and UI should only be updated on the UI thread. You already moved the work on a thread pool thread (via Task.Run), so the only problem you need to solve is how to update the UI from inside the worker. There are many ways to do that - using Control.Invoke/BeginInvoke, SynchronizationContext etc, but the preferred approach for TPL is to pass and use IProgress<T> interface. Don't be fooled by the name - the interface is an abstraction of a callback with some data. There is a standard BCL provided implementation - Progress<T> class with the following behavior, according to the documentation
Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed.
i.e. perfectly fits in UI update scenarios.
With all that being said, here is how you can apply that to your code. We'll use IProgress<string> and will call Report method and pass the full name for each file/directory we find - a direct replacement of your testTaskUpdateLabel calls.
private void button1_Click(object sender, EventArgs e)
{
var progress = new Progress<string>(text => label4.Text = text);
Task.Run(() =>
{
WalkDirectory(new DirectoryInfo(drive), progress);
});
}
public bool WalkDirectory(DirectoryInfo directory, IProgress<string> progress)
{
if (directory == null) throw new ArgumentNullException("directory");
if (progress == null) throw new ArgumentNullException("progress");
return WalkDirectories(directory, progress);
}
bool WalkDirectories(DirectoryInfo directory, IProgress<string> progress)
{
// ...
if (!(continueScan = WalkDirectories(subDirectory, progress)))
// ...
if (continueScan)
progress.Report(directory.FullName);
// ...
}
bool WalkFilesInDirectory(DirectoryInfo directory, IProgress<string> progress)
{
// ...
try
{
progress.Report(file.FullName);
}
// ...
}
I got it to work by making the walkDirectory, walkDirectories and WalkFiles methods async. Thus using the await keyword before I call the testUpdate and testUpdateLabel methods. This way the listview is updated with the search results while the search is running without blocking the UI thread. I.E. the user can cancel the search when the file he was searching for has been found.

Application dispatcher invoke freezes the application

I am having problem with the application freezing. Let me explain my scenario, I have a service which does an async call to a database to get a list of items, It is run by a task. Inside this task I have a try catch block, so it looks like this
public Task<List<T>> ComboListAsync(int? id = null, EnumDTO dto = EnumDTO.Default)
{
return Task.Run(() =>
{
using (var context = new ContextService())
{
try
{
return GetComboList(id, dto, context);
}
catch (Exception e)
{
Handler.DatabaseConnectionException();
throw;
}
}
});
}
Then it throws an exception as GetComboList its just this (for the moment)
protected virtual List<T> GetComboList(int? id, EnumDTO dto, ContextService context)
{
throw new NotImplementedException();
}
So the call catches the exception and goes inside here
public void Show(string message)
{
Message = message;
Application.Current.Dispatcher.Invoke(() =>
{
dialogView = new DialogView() {DataContext = this, Owner = Application.Current.MainWindow};
dialogView.ShowDialog();
});
}
Now the Dispatcher freezes the app, I tried to change it to use begin invoke, it does the same. Without the dispatcher I get an error message that the calling thread is not a STA. I simply want to display my message in a dialog window, that there was a problem connecting to a database. Can anyone help?
I looked online and there is many threads about dispatcher, but none actually show a solution that will fix my issue.
Thank you
EDIT
Code which calls the ComboListAsync
protected override void RetrieveRelatedActiveLists()
{
MyCollection = service.ComboListAsync().Result;
}
Its a deadlock because of the calling code is using the .Result.
Using service.ComboListAsync().Result makes the UI thread await for this method to return, when you call Application.Current.Dispatcher.Invoke from within it you are sending a message to the UI thread that is awaiting the return of method itself.
You must await the method service.ComboListAsync() like this:
protected override async void RetrieveRelatedActiveLists()
{
MyCollection = await service.ComboListAsync();
}

WPF Dispatcher.BeginInvoke Throw Exception

I am using Threadpool.QueueUserWorkItem like following
public void TestMain()
{
try
{
ThreadPool.QueueUserWorkItem(x =>
{
this.Dispatcher.BeginInvoke(new Action(() => this.BackGroundMethod()));
}
);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void BackGroundMethod()
{
try
{
int a = 0;
int b = 100;
var error = b / a;
}
catch(Exception)
{
throw;
}
}
By this way, TestMain() can not catch exception.
Program will shut down...
How can i catch this error?
Thx.
The reason is this.Dispatcher.BeginInvoke(new Action(() => this.BackGroundMethod)); executes asynchonously, so it will finished execute all code inside TestMain before BackGroundMethod is executed.
Use Dispatcher.UnhandledException event to catch the exception in the TestMain() method like this:
Dispatcher.UnhandledException += (sender, args) => MessageBox.Show(args.Exception.Message);
ThreadPool.QueueUserWorkItem(ignore => Dispatcher.Invoke(new Action(BackGroundMethod)));
Edit: Remember to set the Handled property to prevent the internal exception handler from being called:
Dispatcher.UnhandledException += (sender, args) =>
{
MessageBox.Show(args.Exception.Message);
args.Handled = true;
}
To prevent the crash, add a try/catch around the operation which might throw:
ThreadPool.QueueUserWorkItem(x =>
{
try {
this.Dispatcher.BeginInvoke(new Action(() => this.BackGroundMethod()));
}
catch (DivideByZeroException ex) {
// handle the error somehow
}
});
However, think about what you are doing here: you are pushing some action to the thread pool, and in turn it pushes the same action to the dispatcher thread. Why not do this yourself directly without the QueueUserWorkItem call?
You can catch all exceptions in all threads within the application by handling AppDomain.UnhandledException
When you use the Dispatcher to create a new thread, that thread has it's own stack, and so the exceptions wont bubble to the try...catch in TestMain, but instead they will originate in BackgroundMethod method. As you are throwing the exception in BackgroundMethod your try catch is useless, and so if you were to not throw the exception in BackgroundMethod your program wouldn't shut down.
private void BackGroundMethod()
{
try
{
int a = 0;
int b = 100;
var error = b / a;
}
catch(Exception)
{
MessageBox.Show(ex.Message);
}
}
You can do this by catching the exception in the BeginInvoke function and saving it in a temporary variable. After the await you can then rethrow it on the correct thread.
Exception thrown = null;
await Application.Current.Dispatcher.BeginInvoke(new Action<Action>(x =>
{
try
{
BackGroundMethod();
}
catch (Exception ex)
{
//exceptions on this thread MUST be caught or the program will exit
//we will save it, and then when we are back on the main thread we will rethrow it
thrown = ex;
}
}));
if (thrown != null)
throw thrown; //<--- I'm rethrowing it right here on the correct thread

Resources