Search code examples
c#asynchronousasync-awaiticommand

Executing I command asynchronously


For the sake of simplicity I reproduced my Xamarin nUnit testing error as a console aplication and it shows the same problem that I cannot understand. So first the code that works and second the code that doesn't work.

Simple console app

public class Working
{

    private MyViewModel _viewModel;

    public Working()
    {
        Console.WriteLine("Start");
        _viewModel = new MyViewModel();
    }

    static void Main(string[] args)
    {
        Working prog = new Working();
        prog.Print();

    }

    public void Print()
    {
        _viewModel.NewSurveyCommand.Execute(null);
    }
}

public class MyViewModel 
{
    public MyViewModel()
    {
        NewSurveyCommand = new MyCommand(RunTest);
    }

    public ICommand NewSurveyCommand { get; private set; }

    private void RunTest()
    {
        Console.WriteLine("Running...");
        Thread.Sleep(1000);
        Console.WriteLine("Test done");
    }
}

public class MyCommand : ICommand
{
    private Action _action;

    public MyCommand(Action action)
    {
        _action = action;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _action.Invoke();
    }
}

This works fine, the console prints running... then prints test done in one second. Now the second async version which only prints running...

 public class Program
 {

    private ViewModel _viewModel;

    public Program()
    {
        Console.WriteLine("Start");
        _viewModel = new ViewModel();
    }

    static void Main(string[] args)
    {
        Program prog = new Program();
        prog.Go();

    }

    async void Go()
    {
        await Print();
    }

    public async Task Print()
    {
        await Task.Run( () =>  _viewModel.NewSurveyCommand.Execute(null) );
    }
}

public class ViewModel 
{
    public ViewModel()
    {
        NewSurveyCommand = new Command(async () => await RunTest());
    }

    public ICommand NewSurveyCommand { get; private set; }

    public async Task RunTest()
    {
        Console.WriteLine("Running...");
        await Task.Run( () => Thread.Sleep(1000));
        Console.WriteLine("Test done");
    }
}

public class Command : ICommand
{
    private Action _action;

    public Command(Action action)
    {
        _action = action;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _action.Invoke();
    }
  }
}

So the second case executes only part of the code, when it gets to await Task.Run( () => Thread.Sleep(1000)); it just leaves the method to never come back. I don't understand why and how to solve that. Has anyone ever come across the same problem. Thanks.


Solution

  • The main thread terminates before Thread.Sleep(1000); has finished and so do all child threads. You can try to add a Thread.Sleep(2000); at the end of your Main method or let it do something else. It should work then. Also have a look at Microsoft's Task class documentation:

    Waiting for one or more tasks to complete

    Because tasks typically run asynchronously on a thread pool thread, the thread that creates and starts the task continues execution as soon as the task has been instantiated. In some cases, when the calling thread is the main application thread, the app may terminate before any the task actually begins execution. In others, your application's logic may require that the calling thread continue execution only when one or more tasks has completed execution. You can synchronize the execution of the calling thread and the asynchronous tasks it launches by calling a Wait method to wait for one or more tasks to complete.

    I hope this helps.

    Edit:
    You should better use Task.Wait() instead of Thread.Sleep() because often you don't know when a thread will finish:

    static void Main(string[] args)
    {
        Program prog = new Program();
        Task t = prog.Print();
        t.Wait();
    }
    

    This doesn't work because you start a new thread in RunTest(). Then the thread created in Print() returns and unblocks the main thread which returns and terminates every thread. You could solve this by running Thread.Sleep() in RunTest() synchronously. Everything would look like this:

    public class Program
    {
    
        private ViewModel _viewModel;
    
        public Program()
        {
            Console.WriteLine("Start");
            _viewModel = new ViewModel();
        }
    
        static void Main(string[] args)
        {
            Program prog = new Program();
            Task t = prog.Print();
            t.Wait();
        }
    
        async void Go()
        {
            await Print();
        }
    
        public async Task Print()
        {
            await Task.Run(() => _viewModel.NewSurveyCommand.Execute(null));
        }
    }
    
    public class ViewModel
    {
        public ViewModel()
        {
            NewSurveyCommand = new Command(() => RunTest());
        }
    
        public ICommand NewSurveyCommand { get; private set; }
    
        public void RunTest()
        {
            Console.WriteLine("Running...");
            Thread.Sleep(1000);
            Console.WriteLine("Test done");
        }
    }
    
    public class Command : ICommand
    {
        private Action _action;
    
        public Command(Action action)
        {
            _action = action;
        }
    
        public event EventHandler CanExecuteChanged;
    
        public bool CanExecute(object parameter)
        {
            return true;
        }
    
        public void Execute(object parameter)
        {
            _action.Invoke();
        }
    }