1

I have a WPF application. If it is started with some command line parameter, it should only do some database operations and then close itself. This is done in the App.xaml.cs:

public partial class App : Application
{
  protected override async void OnStartup(StartupEventArgs e)
  {
    base.OnStartup(e);
    if(Environment.GetCommandLineArgs().Any(x => x == "blabla"))
    {
      await PerformDatabaseOperations()
      Environment.Exit(0);   
    }
    
    var mainWindow = new MainWindow();
    mainWindow.Show();
    this.MainWindow = mainWindow;
  }
}

The problem with this code is that the method PerformDatabaseOperations() is not executed completely. The moment it hits the first actual database operation, the program exits. I assume this is because OnStartup doesn't return a Task and thus can't be awaited. Because of this, the line Environment.Exit(0); is executed when the first asynchronous operation is encountered inside this method.

Is there a way to fix this?

SomeBody
  • 7,515
  • 2
  • 17
  • 33
  • impement loading screen, which will be visible until db operation is done, and then show mainwindow – ASh Sep 15 '22 at 09:39
  • 1
    Related: [Calling async Web API method from App.OnStartup](https://stackoverflow.com/questions/28968015/calling-async-web-api-method-from-app-onstartup) and also [WPF App, run async task before opening Window](https://stackoverflow.com/questions/49701102/wpf-app-run-async-task-before-opening-window). Surprisingly there is no reference about the application terminating prematurely in these questions. – Theodor Zoulias Sep 15 '22 at 11:34

2 Answers2

2

You can call async Methods from within a synchronous method like this:

YourAsyncMethod().Wait();

So you can make your OnStartup-Method synchronous and perform your database operations like this:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    if(Environment.GetCommandLineArgs().Any(x => x == "blabla"))
    {
        PerformDatabaseOperations().Wait();
        Environment.Exit(0);   
    }

    var mainWindow = new MainWindow();
    mainWindow.Show();
    this.MainWindow = mainWindow;
}
jeanluc162
  • 428
  • 1
  • 10
  • I wanted to avoid this because as far as I know it might lead to deadlocks. Are you sure I can use it without any trouble? – SomeBody Sep 15 '22 at 09:49
  • I can't say for sure as I don't know the inside of your PerformDatabaseOperations()-Method. If you want to be sure, rename your method to PerformDatabaseOperationsAsync() and reimplement PerformDatabaseOperations() using synchronous Equivalents for every operation you need to perform. That's the cleanest solution imo – jeanluc162 Sep 15 '22 at 09:51
  • 1
    @SomeBody yes, it's safe. You can confirm that the [`SynchronizationContext.Current`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext.current) is `null` at this moment, so any `await` operations inside the `PerformDatabaseOperations` won't capture a `SynchronizationContext`. And without context there is no risk of [deadlock](https://stackoverflow.com/questions/15021304/an-async-await-example-that-causes-a-deadlock "An async/await example that causes a deadlock"). – Theodor Zoulias Sep 15 '22 at 09:53
1

From the point of view of the application, your OnStartup method returns when the first await is hit. At that point it has not created or shown any windows.

My guess is that this causes the application to terminate since there is no message loop started. You could check your theory placing a breakpoint on the Environment.Exit(0), my guess is that this will not be hit.

Creating the mainWindow before doing your database operation might solve the issue, but will result in a empty window being shown, but I'm not sure if there is any good workaround for that problem. When creating a similar application I went the other way, using a console program that shows a UI window, leaving a console box in addition to the UI.

Note that you should be very careful to handle any exception when using async void, since these will otherwise be lost.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • *"Note that you should be very careful to handle any exception when using `async void`, since these will otherwise be lost."* -- Do you mean that unhandled exceptions inside `async void` event handlers are suppressed, and cannot be observed? Are you sure about this? – Theodor Zoulias Sep 15 '22 at 09:41
  • @TheodorZoulias `async` transforms the method into a state machine, so any code after the first await will be invoked asynchronously from the threadpool or message pump. If that code throws an exception there will be nothing above it in the call stack that can handle it. Normally the exception would forwarded in the returned task, but an `async void` does not return any task, so it needs to handle exceptions itself. – JonasH Sep 15 '22 at 09:50
  • 1
    Check out [this](https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void "Async/Await - Best Practices in Asynchronous Programming") article, under the **Avoid Async Void** section: *"[...] any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started."* – Theodor Zoulias Sep 15 '22 at 09:57
  • You are right, the breakpoint at `Environment.Exit(0)` is not reached. I solved the problem with jeanlucs62's solution. Thanks a lot for your answer. – SomeBody Sep 15 '22 at 09:58
  • @TheodorZoulias I would argue "raised directly on the SynchronizationContext" is equivalent to lost, since most do not use the global catch all exception handler, and doing that is not recommended for maintainability reasons anyway. – JonasH Sep 15 '22 at 11:57
  • Have you validated experimentally this hypothesis? Last time I checked, an unhandled exception in an `async void` method makes its presence felt in the loudest way possible: it crashes the process. IMHO before making recommendations or giving advices, it's a good idea to establish with certainty what the facts are. – Theodor Zoulias Sep 15 '22 at 12:07
  • @TheodorZoulias I really do not see any issue with my recommendation that you should be careful with handling exceptions in async void methods, your own link seem to support as much. – JonasH Sep 15 '22 at 12:10
  • Your recommendation in the answer is based on the argument *"[...] since these will otherwise be lost."* I am not challenging the recommendation. I am challenging the argument. I think that it's incorrect. – Theodor Zoulias Sep 15 '22 at 12:14
  • @TheodorZoulias I think you are really splitting hairs, having an exception escape and crash the process would be 'Lost' as far as I'm concerned, since it is your last chance to catch it. How do you think the meaning should be expressed to capture all the relevant technical details you think are important? – JonasH Sep 15 '22 at 12:29