繁体   English   中英

C#——将同步程序转换为异步程序

[英]C# - converting synchronous program into asynchronous program

我目前正在重写我的代码以添加异步执行。 但是,我在调整一组方法时遇到了麻烦。

在提问之前,我查看了一些类似这样的问题,但问题通常是使用 Task.Run() 而不是 async/await。

=======摘要=======

我的程序将用于自动化带有多个阀门的测试台。 因此,我编写了诸如open/closeValve() 之类的方法,可将阀门打开或关闭到某个阈值,将 Flow () 增加/减少到 select 以打开或关闭哪个阀门,以及调用 increqse/decreaseFlow() 的调节 ()方法。

就在最近,在阅读了它之后,我决定添加异步执行是一个实时显示有关工作台数据的好主意。 但是我的方法都被认为是以同步方式工作的,所以我很难适应它们。

=======代码=======

例如这里是 openValve() 方法

public int openValve(int limit)
    {
        if(_masterFlowrate >= _instructions - 0.1 && _masterFlowrate <= _instructions + 0.1)
        {
            //flowrate ~= instructions => no need to manipulate the valves
            return 1;
        }
        else
        {
            if(valveOpening < limit)
            {
                //flowrate < instructions & valve opened less than the limit
                // => opening valve untill the limit or untill flowrate = instructions
                valveOpening++;
 
                //Opening the valve by writing the value in a command computer via Modbus
                master.WriteSingleRegister(1, _mdbAdr, Convert.ToUInt16(ouvertureValve));
                return 0;
            }
            else
            {
                //flowrate < instructions & valve already opened beyond the limit
                // => not manipulating the valve
                return -1;
            }
        }

为了使该方法适应异步执行,我将返回值类型从int更改为Task<int>并将return 1更改为return Task.FromResult(1)例如。

现在直接使用上一个的 increaseFlow() 方法看起来是这样的(Vp、Vm 和 Vg 是 Valve 类的实例)

 public int increaseFlow()
    {
        int retVal = 0;
        switch(openingStep)
        {
            case 1:
                retVal = Vp.openValve(60);
                if(retVal > 0) openingStep = 4;
                else
                {
                    if(retVal < 0) openingStep = 2;
                }
                break;

            case 2:
                retVal = Vg.openValve();
                if(retVal > 0) openingStep = 4;
                else
                {
                    if(retVal < 0) openingStep = 3;
                }
                break;

            case 3:
                retVal = Vm.openValve();
                if(retVal > 0) openingStep = 4;
                else
                {
                    if(retVal < 0) openingStep = 4;
                }
                break;
        }

        return retVal;
    }

这就是我开始遇到麻烦的地方。 我试过改变int retVal = 0; 进入var retVal = Task.FromResult(0)不会给我任何编译器错误。

然后在每个 openValve() 之前添加 awaits 关键字,但这给了我以下信息:

Error   CS0029  Cannot implicitly convert type 'int' to 'System.Threading.Tasks.Task<int>'

然后regular() 方法调用increaseFlow()。

=======结论=======

这就是我开始认为代码可能没有考虑到异步工作并且需要重新排列的地方。 这真的是我的问题,还是有不需要完全重做方法的解决方案。 我并不是说我不会这样做,但我很想知道结构是否错误,或者我只是没有使用正确的关键字/方法等......

添加异步执行是实时显示有关工作台数据的好主意

这并不是异步执行的真正目的。 异步就是释放线程,这在客户端转化为响应式 UI,在服务器端转化为更具可扩展性的服务。

关于“实时显示有关工作台的数据”,听起来您真正想要的是一种进度报告形式。 (请注意,此上下文中的“进度”可以是任何数据集合,而不仅仅是完成百分比)。 内置的IProgress<T>是为进度报告而设计的,即使大多数示例都是针对异步代码的,也可以很好地与同步代码配合使用。

如果您的应用主要发出 I/O 绑定请求,您需要异步代码。 但是使某些东西异步的正确方法是从最低级别开始,例如WriteSingleRegister ,然后让异步从那里通过应用程序发展起来。 如果您正在控制硬件,异步是可能的,但可能不是最佳解决方案。

一个可能的原因是async Task<T>Task<T>之间的区别。 IE

public async Task<int> MyMethod(){
    return 42; // Task creation is implicit
}

public Task<int> MyMethod(){
    return Task.FromResult(42); // Task creation is explicit
}

更大的问题是为什么要使用异步方法重写系统。 通常有几个主要原因:

  1. 您正在使用某种 IO、磁盘、网络等。异步方法有助于避免在使用这些方法时阻塞线程。
  2. 您正在使用计算密集型的东西,并且不想在 CPU 处理几秒钟时阻塞 UI。
  3. 在线程中包装阻塞 IO 调用以避免阻塞 UI。 (当真正的异步 IO api 不可用时)。

只是将方法更改为异步并使用Task.FromResult可能根本不会带来任何优势,因为所有代码仍将在主线程上运行,并且任何 CPU 密集型操作或阻塞调用仍将冻结 UI。

如果目标是避免 UI 阻塞,那么Task.Run可能是通往 go 的方法。 但这将要求您的程序是线程安全的。 您可以使用limitedConcurrencyTaskScheduler在后台线程上移动工作,但仍会阻止同时执行多个命令。 这降低了线程安全问题的风险。

总的来说,我建议重新分析您要解决的实际问题。 使用异步模式可能是这种解决方案的一部分,但它可能不是唯一的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM