具有任务控制功能的异步控制台

时间:2015-10-06 14:03:25

标签: c# .net asynchronous console task-parallel-library

请帮我找到合适的解决方案。 程序等待完成通过控制台,同时监控任务的主要问题。

我写了一些原型,但我不确定它是否有效 - 在这种方法中,我们花费额外的线程等待来自控制台的操作。我没有看到替代方案,因为Console不支持异步(某种 Console.ReadLineAsync )。

更新: 我有两个工作任务( task1,task2 )。他们模拟一些实际工作。 该程序是一个控制台。因此,我们需要让用户有机会停止该程序。默认情况下,通过按“Enter”(通过 consoleTask )的期望来完成此操作。

问题是。如何等待用户完成工作线程和监视停止命令。

static void Main(string[] args)
{
  CancellationTokenSource mycts = new CancellationTokenSource();
  var task1 = Task.Run(() =>
  {
  // doing some work, that can throw exception
  Thread.Sleep(1000);

  // how to avoid this closuring ?
  mycts.Token.ThrowIfCancellationRequested();

  throw new InvalidOperationException("test");
  }).ContinueWith((_) => mycts.Cancel()); // Do I need caching this task?

  var task2 = Task.Run(() =>    
  {
  // doing some work, that can throw exception
  Thread.Sleep(5000);

  // again closuring
  mycts.Token.ThrowIfCancellationRequested();
  throw new InvalidOperationException("test");
  }).ContinueWith((_) => mycts.Cancel()); // Do I need caching this task?


  // I do not know how to do better with Console !!
  var consoleTask = Task.Factory.StartNew((cts) =>
  {
  Console.WriteLine("Press Enter to exit");
  Console.ReadLine();
  }, mycts).ContinueWith((_) => mycts.Cancel()); // Do I need caching this task?

  // Waiting for the Completion or Exception
  Task.WaitAny(task1, task2, consoleTask);

  // Now waiting for the completion of workflow
  try
  {
    Task.WaitAll(task1, task2);
  }
  catch (Exception ex)
  {
    // log faulted tasks
  }

  //Exit
  }

2 个答案:

答案 0 :(得分:2)

您应该遵循一些指导原则:

  1. 请勿使用ContinueWith。请改用await
  2. 请勿使用Task.Factory.StartNew。请改用Task.Run
  3. 不要混用阻塞和异步代码。对于控制台应用程序,通常最好让Main调用MainAsync并等待返回的任务。对于大多数应用程序来说,这是您应该使用的唯一阻止。
  4. 我不确定how to avoid this closuring?的含义。

    对于ReadLine(和其他Console方法),你是对的,遗憾的是没有异步方法。 可能可以使用单独的线程,但Console类(更具体地说,控制台输入和输出流)有一些不寻常的锁定,所以我不是肯定这会起作用:

    static void Main(string[] args)
    {
      MainAsync().Wait();
    }
    static CancellationTokenSource mycts = new CancellationTokenSource();
    static async Task MainAsync()
    {
      try
      {
        var task1 = CancelAfterSuccessfulCompletionAsync(
            Task.Run(() => SomeWorkThatCanThrowException()));
        var task2 = CancelAfterSuccessfulCompletionAsync(
            Task.Run(() => OtherWorkThatCanThrowException()));
        var consoleTask = CancelAfterSuccessfulCompletionAsync(
            Task.Run(() => MonitorConsole()));
        await Task.WhenAll(task1, task2, consoleTask);
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex);
      }
    }
    static void OtherWorkThatCanThrowException()
    {
      Thread.Sleep(5000);
      mycts.Token.ThrowIfCancellationRequested();
      throw new InvalidOperationException("test");
    }
    static void SomeWorkThatCanThrowException()
    {
      Thread.Sleep(1000);
      mycts.Token.ThrowIfCancellationRequested();
      throw new InvalidOperationException("test");
    }
    static void MonitorConsole()
    {
      Console.WriteLine("Press Enter to exit");
      Console.ReadLine();
    }
    static async Task CancelAfterSuccessfulCompletionAsync(Task task)
    {
      await task;
      mycts.Cancel();
    }
    

答案 1 :(得分:1)

由于控制台没有SynchronizationContext,因此在执行异步操作时,如果不阻塞主线程,就无法做到这一点。

但是,如果您只是将代码编写为异步并以最简单的方式阻塞,则会更简单。我建议将所有代码移到异步MainAsync并阻止一次:

static void Main()
{
    MainAsync().Wait();
}

static async Task MainAsync()
{
    // manage tasks asynchronously
}

您可以使用自定义上下文来执行Stephen Cleary's AsyncContext之类的异步操作,而不是阻塞。这样可以避免在Task上同步阻止:

static void Main()
{
    AsyncContext.Run(MainAsync);
}