【问题标题】:Using Task.Run() for hardware interfacing thread invocations使用 Task.Run() 进行硬件接口线程调用
【发布时间】:2016-09-16 09:18:57
【问题描述】:

我需要调用一个满足以下条件的方法。

  • 该方法可能会运行数小时。
  • 该方法可以与硬件接口。
  • 该方法可能会请求用户输入(参数值、确认等)。该请求应阻止该方法,直到收到输入为止。

我有一个使用以下设计满足此标准的原型实现。

假设存在Form 并包含Panel

IntegerInput 类是一个 UserControl 和一个 TextBox 和一个 Button

public partial class IntegerInput : UserControl
{
  public TaskCompletionSource<int> InputVal = new TaskCompletionSource<int>(0);

  public IntegerInput()
  {
    InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
    int val = 0;

    Int32.TryParse(textBox1.Text, out val);

    InputVal.SetResult(val);
  }
}

Form1UserInput 类由Form1 实例化。 container 是由Form1 在提供给调用类之前设置的Panel

public interface IUserInput
{
  Task<int> GetInteger();
}

public class Form1UserInput : IUserInput
{
  public Control container;

  private IntegerInput integerInput = new IntegerInput();
  public IntegerInput IntegerInput { get { return integerInput; } }

  public async Task<int> GetInteger()
  {
    container.Invoke(new Action(() => 
      { 
        container.Controls.Clear();
        container.Controls.Add(integerInput); 
      }));

    await integerInput.InputVal.Task;

    return integerInput.InputVal.Task.Result;
  }
}

Demo 类包含我要调用的方法。

public class Demo
{
  public IUserInput ui;

  public async void MethodToInvoke()
  {
    // Interface with hardware...

    // Block waiting on input
    int val = await ui.GetInteger(); 

    // Interface with hardware some more...
  }

  public async void AnotherMethodToInvoke()
  {
    // Interface with hardware...

    // Block waiting on multiple input
    int val1 = await ui.getInteger();
    int val2 = await ui.getInteger();

    // Interface with hardware...
  }
}

这是调用类的大致轮廓。对于我的原型,对Task.Run() 的调用是准确的。

public class Invoker
{
  public async Task RunTestAsync(IUserInput ui)
  {
    object DemoInstance = Activator.CreateInstance(typeof(Demo));
    MethodInfo method = typeof(Demo).GetMethod("MethodToInvoke");
    object[] args = null;

    ((IUserInput)DemoInstance).ui = ui;

    var t = await Task.Run(() => method.Invoke(DemoInstance, args));

    // Report completion information back to Form1
  }
}

Form1 控制器类实例化Invoker 并调用RunTestAsync 传递Form1UserInput 的实例。

我知道一些关于长时间运行的任务可能会阻塞以及这对ThreadPool 资源意味着什么的担忧。但是,我正在构建的应用程序不提供一次调用多个方法的能力。当调用的方法正在运行时,应用程序可能会提供一些其他有限的功能,但当前的要求并未详细指定此类功能。我预计任何时候都只会有一个长时间运行的线程在服务中。

对这种类型的方法调用使用 Task.Run() 是合理的实现吗?如果不是,那么提供所需标准的更合理的实施是什么?我是否应该为此调用考虑在 ThreadPool 之外使用专用线程?

【问题讨论】:

  • 停止async void 唯一可以使用的情况是当您有事件处理程序并且您无法更改返回类型时。

标签: c# multithreading async-await task-parallel-library .net-4.5


【解决方案1】:

对这种类型的方法调用使用 Task.Run() 是一个合理的实现吗?

假设您的“硬件接口”只能使用 同步 API 完成,那么是的,Task.Run 就可以了。

但是,我会更改 何时 它被调用。现在,Task.Run 正在包装一个在线程池上执行的async void 方法(并使用Invoke 跳回UI 线程)。这些都是有问题的:Task.Run 超过 async void 似乎会“提前”完成(即,在第一个 await);并且使用Invoke 表示正在进行一些紧密耦合(UI 调用调用 UI 的后台服务)。

我会将async void 替换为async Task,并更改Task.Run 用于避免Invoke 的位置:

public async Task<int> GetInteger()
{
  container.Controls.Clear();
  container.Controls.Add(integerInput); 

  // Note: not `Result`, which will wrap exceptions.
  return await integerInput.InputVal.Task;
}


public async Task MethodToInvokeAsync()
{
  await Task.Run(...); // Interface with hardware...

  // Block waiting on input
  int val = await ui.GetInteger(); 

  await Task.Run(...); // Interface with hardware some more...
}


var t = await (Task)method.Invoke(DemoInstance, args);

【讨论】:

  • 您所说的“Task.Run 超过 async void 似乎会“提前”完成是什么意思? awaitasync void 上的功能是否不同?
  • @OttPrime: async void 不返回“句柄”,因此Task.Run 无法知道该方法何时完成。所以Task.Run 返回的任务会在它遇到第一个await 时完成,而不是在整个async void 方法完成时完成。
  • 我实施了你的建议,我明白你对不需要调用的意思。但是,我有需要多个输入的方法,在这些情况下,第二个 awaited 块在收到第一个输入后立即运行,而不是阻塞所有输入。我在上面的Demo 类中添加了一个示例为AnotherMethodToInvoke。对ui.GetInteger() 的第二次调用是否应该继续阻止?
  • 我想我看到了这个问题。我试图通过调用GetInteger 重用相同的Task 实例。我修改了我的生产代码以使调用构造一个新的TaskCompletionSource 并将其作为Task 返回到`await.它与该实现一起使用。
猜你喜欢
  • 2014-12-03
  • 1970-01-01
  • 1970-01-01
  • 2015-08-23
  • 2014-10-13
  • 1970-01-01
  • 2020-09-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多