【问题标题】:How to serialize access to an asynchronous resource?如何序列化对异步资源的访问?
【发布时间】:2011-02-03 11:54:32
【问题描述】:

谁能为我指出以下问题的有效解决方案?

我正在处理的应用程序需要通过 TCP 与在另一个系统上运行的软件进行通信。我发送到该系统的一些请求可能需要很长时间才能完成(最多 15 秒)。

在我的应用程序中,我有许多线程,包括主 UI 线程,它们可以访问与远程系统通信的 服务。只有一个服务实例可供所有线程访问。

我只需要允许一次处理一个请求,即它需要被序列化,否则 TCP 通信会发生不好的事情。

目前尝试的解决方案

最初我尝试使用带有静态对象的 lock() 来保护每个“命令”方法,如下所示:

lock (_cmdLock)
{
SetPosition(position);
}

但是我发现有时它不会释放锁,即使远程系统和 TCP 通信上存在超时。此外,如果来自同一个线程的两个调用(例如,用户双击一个按钮),那么它将通过锁定 - 在再次阅读有关锁定之后,我知道同一个线程不会等待锁定。

然后我尝试使用 AutoResetEvents 一次只允许一个呼叫。但是如果没有锁定,它将无法与多个线程一起使用。以下是我用来发送命令(来自调用线程)和处理命令请求(在自己的线程上在后台运行)的代码

   private static AutoResetEvent _cmdProcessorReadyEvent = new AutoResetEvent(false);
   private static AutoResetEvent _resultAvailableEvent = new AutoResetEvent(false);
   private static AutoResetEvent _sendCommandEvent = new AutoResetEvent(false);

   // This method is called to send each command and can run on different threads
   private bool SendCommand(Command cmd)
    {
       // Wait for processor thread to become ready for next cmd
       if (_cmdProcessorReadyEvent.WaitOne(_timeoutSec  + 500))
            {
            lock (_sendCmdLock)
                 {
                 _currentCommand = cmd;
                 }

            // Tell the processor thread that there is a command present
            _sendCommandEvent.Set();

            // Wait for a result from the processor thread
            if (!_resultAvailableEvent.WaitOne(_timeoutSec  + 500))
                _lastCommandResult.Timeout = true;

        }
        return _lastCommandResult.Success;
    }

 // This method runs in a background thread while the app is running
 private void ProcessCommand()
    {
        try
        {
            do
            {
                // Indicate that we are ready to process another commnad
                _cmdProcessorReadyEvent.Set();

                _sendCommandEvent.WaitOne();
                lock (_sendCmdLock)
                {
                    _lastCommandResult = new BaseResponse(false, false, "No Command");
                    RunCOMCommand(_currentCommand);
                }

                _resultAvailableEvent.Set();

            } while (_processCommands);
        }
        catch (Exception ex)
        {
            _lastCommandResult.Success = false;
            _lastCommandResult.Timeout = false;
            _lastCommandResult.LastError = ex.Message;
        }

    }

我还没有尝试实现命令请求队列,因为调用代码期望一切都是同步的——即,在我发送下一个命令之前,上一个命令必须已经完成。

附加背景

远程系统上运行的软件是第 3 方产品,我无权访问它,它用于控制带有集成 XY 工作台的激光打标机。

我实际上使用的是旧版 VB6 DLL 与激光器通信,因为它具有用于格式化命令和处理响应的所有代码。此 VB6 DLL 使用 WinSock 控件进行通信。

【问题讨论】:

  • 它控制激光!它是附在鲨鱼头上的吗?!?你有迷你我吗? ;)

标签: c# multithreading tcp


【解决方案1】:

我不确定为什么排队解决方案不起作用。

为什么不将每个请求以及带有结果的回调的详细信息放在队列中?您的应用程序将对这些请求进行排队,与您的第 3 方系统接口的模块可以依次获取每个队列项、处理并返回结果。

我认为这是模块之间更清晰的关注点分离,而不是围绕请求调度等实现锁定。您的请求者在很大程度上忘记了序列化约束,并且第 3 方接口模块可以处理序列化、管理超时和其他错误等.

编辑:在 Java 世界中,我们有 BlockingQueues,它们为消费者/发布者同步,使这类事情变得非常容易。我不确定您在 C# 世界中是否也有同样的情况。快速搜索表明没有,但是这种东西有源代码(如果 C# 世界中的任何人都可以提供一些启发,将不胜感激)

【讨论】:

  • 我完全同意排队解决方案。
  • 是的,使用排队解决方案,它的额外好处是可以轻松监控等待请求的数量。
  • 队列存在潜在的计时问题,例如我需要将激光移动到特定位置,打开它,然后通过 RS232 发送单独的命令以读取激光的功率另一个设备。如果我将请求放在一个队列中,我将不知道它何时会被执行,然后我就无法将它与功率计读数协调起来。当我需要在后续命令中使用时,一些命令会返回数据(例如获取 XY 位置),所以我真的需要一次只执行一个命令。
  • 所以也许 a) 您的命令不只是 基于 TCP 的命令,还有 RS232(使用命令模式或类似模式)b) 您不会排队命令,直到上一个命令的回调为您提供了该 XY 信息?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-04
  • 2021-02-05
  • 1970-01-01
  • 1970-01-01
  • 2017-01-06
  • 2021-05-01
  • 1970-01-01
相关资源
最近更新 更多