1

I created a very simple service for this example, and it boggles my mind that it works without fail in a console application, but waits indefinitely in a Wpf application.

To try and make things as consistent as possible, both applications have NLog and MvvmLight referenced.

The Window part of the Wpf application is bypassed for this example, this is to simulate the async Task doing work before the GUI is loaded and to show that my problem is essentially when the service call is made from the App.xaml.cs file. For the record, running this task off a Command from a Button on the Window works just fine in Wpf. That's why I am comparing this to a console application.

My suspicion here is that somehow the UI Thread is playing a part, but as I said there is no Window being created as a result of the way I created this example.

Wpf Application

The application hangs on await Task.Delay(delay); in ThingService never returning result. I verified this by setting a breakpoint on that line, and stepping over it. It's like the Task never finishes, but considering it's nothing more than a 1 second delay that seems unlikely.

App.xaml.cs

namespace AsyncWpfExample {
    using System;
    using System.Windows;
    using CommonServiceLocator;
    using NLog;
    using Services;

    public partial class App {
        private Window _window;

        private void Application_Exit(object sender, ExitEventArgs e) {
            var logger = ServiceLocator.Current.GetInstance<ILogger>();

            logger.Info($"STOP {Environment.NewLine}{new string('-', 40)}");
        }

        private void Application_Startup(object sender, StartupEventArgs e) {
            var logger = ServiceLocator.Current.GetInstance<ILogger>();
            logger.Info("Start");

            try {
                var thingService = ServiceLocator.Current.GetInstance<IThingService>();

                logger.Debug("Before doing a thing");

                var result = thingService.DoAThing(1000).Result;

                logger.Debug(result);
                logger.Debug("After doing a thing");

            } catch (Exception ex) {
                logger.Error(ex, "Unknown Error occurred");
            } finally {
                if (_window == null) {
                    Shutdown();
                }
            }
        }
    }
}

ThingService.cs

namespace AsyncWpfExample.Services {
    using System.Threading.Tasks;
    using NLog;

    public interface IThingService {
        Task<string> DoAThing(int delay);
    }

    public class ThingService : IThingService {
        private readonly ILogger _logger;

        public ThingService(ILogger logger) {
            _logger = logger;
        }

        public async Task<string> DoAThing(int delay) {
            _logger.Debug("Start doing a thing");
            await Task.Delay(delay);
            _logger.Debug("Finished doing a thing");

            return $"Waited ${delay}";
        }
    }
}

ViewModelLocator.cs

namespace AsyncWpfExample.ViewModels {
    using CommonServiceLocator;
    using GalaSoft.MvvmLight;
    using GalaSoft.MvvmLight.Ioc;
    using NLog;
    using Services;

    public class ViewModelLocator {
        public ViewModelLocator() {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            if (!SimpleIoc.Default.IsRegistered<ILogger>()) {
                if (!ViewModelBase.IsInDesignModeStatic) {
                    SimpleIoc.Default.Register<ILogger>(LogManager.GetCurrentClassLogger);
                } else {
                    SimpleIoc.Default.Register<ILogger>(LogManager.CreateNullLogger);
                }
            }

            SimpleIoc.Default.Register<IThingService, ThingService>();
        }

        public static void Cleanup() { }
    }
}

Console Application

For simplicity sake, I removed all references to the MainViewModel that comes with MvvmLight. When installing NLog just use the default NLog.config (make sure to copy always/newer though!)

The ViewModelLocator and ThingService are essentially the same as from the Wpf application as well, except for the namespace.

Program.cs

namespace AsyncConsoleExample {
    using System;
    using Services;
    using CommonServiceLocator;
    using NLog;
    using ViewModel;

    class Program {
        static void Main(string[] args) {
            ViewModelLocator.Register();

            var logger = ServiceLocator.Current.GetInstance<ILogger>();
            var thingService = ServiceLocator.Current.GetInstance<IThingService>();


            Console.WriteLine("Before doing a thing");
            logger.Debug("Before doing a thing");

            var result = thingService.DoAThing(1000).Result;

            Console.WriteLine(result);
            logger.Debug(result);

            Console.WriteLine("After doing a thing");
            logger.Debug("After doing a thing");

            Console.ReadLine();
        }
    }
}

ThingService.cs

namespace AsyncConsoleExample.Services {
    using System.Threading.Tasks;
    using NLog;

    public interface IThingService {
        Task<string> DoAThing(int delay);
    }

    public class ThingService : IThingService {
        private readonly ILogger _logger;

        public ThingService(ILogger logger) {
            _logger = logger;
        }

        public async Task<string> DoAThing(int delay) {
            _logger.Debug("Start doing a thing");
            await Task.Delay(delay);
            _logger.Debug("Finished doing a thing");

            return $"Waited ${delay}";
        }
    }
}

ViewModelLocator.cs

namespace AsyncConsoleExample.ViewModel {
    using GalaSoft.MvvmLight;
    using GalaSoft.MvvmLight.Ioc;
    using CommonServiceLocator;
    using NLog;
    using Services;

    public class ViewModelLocator {
        public ViewModelLocator() {
            Register();
        }

        public static void Register() {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            if (!SimpleIoc.Default.IsRegistered<ILogger>()) {
                if (!ViewModelBase.IsInDesignModeStatic) {
                    SimpleIoc.Default.Register<ILogger>(LogManager.GetCurrentClassLogger);
                } else {
                    SimpleIoc.Default.Register<ILogger>(LogManager.CreateNullLogger);
                }
            }

            SimpleIoc.Default.Register<IThingService, ThingService>();
        }

        public static void Cleanup() { }
    }
}

So why is the behaviour different between the Console application and the Wpf application? Unless I missed something obvious, they should be exactly idential except for the App.xaml.cs versus Program.cs parts.

Jeff
  • 1,727
  • 2
  • 17
  • 29
  • 1
    [Don't Block on Async Code](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) – Herohtar Nov 01 '19 at 21:07
  • They differs in Synchronization context: https://devblogs.microsoft.com/pfxteam/await-synchronizationcontext-and-console-apps/ – Ive Nov 01 '19 at 21:09
  • 3
    So declare `private async void Application_Startup(object sender, StartupEventArgs e)` and call `var result = await thingService.DoAThing(1000);` – Clemens Nov 01 '19 at 21:21
  • @Clemens First, I must thank you but I disagree that this is a duplicate question. Just because this question has a similar solution to another question, does not make them equal. The research I conducted and content of my question make it different. Not to mention my research path did not introduce me to the question you found. Anyone who wants to understand the different behaviours between Console apps and .NET UI apps could find value in my question. – Jeff Nov 04 '19 at 18:59

0 Answers0