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.