C#系统。无效操作异常。

8

概述

我正试图用C#编写一个非常简单的TCP/IP客户端服务器,连接到一个IP地址并指定一个端口号,请求一些简单的行命令,然后将回复放置在网格框、图形或其他显示选项中。

我在网上找到了一个可以下载的实用程序,由Jayan Nair编写,它似乎可以正确地发送消息并接收回复。

问题出在当我尝试将回复数据加载到richtext或GridView时。

我得到的错误信息是:System.InvalidOperationException

我已经在Microsoft论坛上求助过,他们给了我一个非常复杂、模糊和过度涉及INVOKE和BeginInvoke的解决方法,整个过程似乎成为一个项目。

我想要的只是一个能够工作的示例,不要太复杂。

这是代码:

        try
        {
            SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
            int iRx = theSockId.thisSocket.EndReceive(asyn);
            char[] chars = new char[iRx + 1];
            System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
            int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
            // 
            System.String szData = new System.String(chars);
            richTextRxMessage.Text =  szData;  // fails
            //textBox1.Text = szData;          // also fails
            WaitForData();
        }

以下是错误信息:

base {System.Windows.Forms.TextBoxBase} = {Text = '((System.Windows.Forms.RichTextBox)    (((System.Windows.Forms.RichTextBox)(richTextRxMessage)))).Text' threw an exception of type   'System.InvalidOperationException'}

附加信息如下: szData包含大约6300个字符,包括制表符(9)和回车(13),与从服务器发送的消息一致。 我还尝试过使用文本框而不是RichText,结果相同。

对于那些感兴趣的人

这是Microsoft的链接

http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/12a67fb0-847e-4e2b-baa8-ef6ceed60aa4/


  • 以下是我尝试的两个代码修改中的2个,都在相同的错误条件下失败
  • 我认为我需要从更低的级别开始C#,而不仅仅是跳进并希望获得最好的结果

  •    public void OnDataReceived(IAsyncResult asyn)
        {
    
            int InputRx;
            int charLen;
            char[] Inputchars;
            System.Text.Decoder InputDecode;
            System.String szData;
            bool IfInvokeRequired;
    
            try
    
            {
    
                SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
                InputRx = theSockId.thisSocket.EndReceive(asyn);                    // get size of input array
                Inputchars = new char[InputRx + 1];                                   // put i char array
                InputDecode = System.Text.Encoding.UTF8.GetDecoder();
                charLen = InputDecode.GetChars(theSockId.dataBuffer, 0, InputRx, Inputchars, 0);
                szData = new System.String(Inputchars);
                IfInvokeRequired = richTextRxMessage.InvokeRequired;
                if (IfInvokeRequired == true)
                {
                    richTextRxMessage.Invoke((MethodInvoker)delegate { this.Text = szData; });// fails
                    richTextRxMessage.BeginInvoke(new MethodInvoker(() => richTextRxMessage.Text = szData));//fails as well
                }
    

    我已经回答了,受到了微软答案的影响,但是一个重要的问题仍然存在:这是否是从后台线程运行的。这是一个非常重要的问题,它将完全改变我的答案。 - Nevyn
    我认为你应该认真看待MSDN对StackOverflow答案的引用。Invoke只需要一行代码就可以解决问题,而不是一个独立的项目。你也应该能够判断是否出现了跨线程问题。有没有内部异常? - criticalfix
    4个回答

    5
    您收到的 InvalidOperationException 可能会有内部异常 "调用线程无法访问此对象,因为不同的线程拥有它"。
    为了调试起见,您可以快速将 CheckForIllegalCrossThreadCalls 设置为 false。这将阻止运行时抛出异常,但是仅仅因为您没有收到异常并不意味着它们没有发生,所以这是分布式软件的不良实践,但对于调试非常方便。
    为了解决您的问题,您需要在 UI 线程 上调用与 RichTextBox 交互的代码,也就是控件创建的线程和唯一允许运行与控件交互的代码的线程。
    要在 UI 线程上调用代码,您可以使用以下简洁语句:
    richTextRxMessage.BeginInvoke(new MethodInvoker(() => richTextRxMessage.Text = szData));
    

    简单来说,您在这里所做的就是通过委托将要在UI线程上调用的语句传递。


    1
    这是我在一些代码中使用的一个函数;
    public static void InvokeEx<T>(this T @this, Action<T> action) where T : ISynchronizeInvoke
    {
        if (@this.InvokeRequired)
        {
            @this.Invoke(action, new object[] { @this });
        }
        else
        {
            action(@this);
        }
    }
    

    这是一种非常好的处理任何调用的小方法,使用它可以减少代码行数。使用方法很简单,只需这样:

    Control.InvokeEx(f => control.DoStuff());
    

    例如,您可能会使用以下内容;
    richTextRxMessage.InvokeEx(f => richTextRxMessage.Text =  szData);
    

    编辑:正如其他人所说,这个错误很可能是由于在创建它的线程之外访问控件造成的,这就是为什么需要调用的原因。

    0

    微软的答案并不完全错误,但有其他更简单的方法可以实现,尽管它们看起来不是那么简单。

    这里是我自己代码中的一小段,可能对你有用。

    if (dirtyImage.InvokeRequired)
    {
        dirtyImage.Invoke(new MethodInvoker(delegate { Visible = RigX.IsDirty; }));
        dirtyImage.Invoke(new MethodInvoker(delegate { Refresh(); }));
    }
    else
    {
        dirtyImage.Visible = RigX.IsDirty;
        dirtyImage.Refresh();
    }
    

    请注意如何使用InvokeRequireddirtyImage.Invoke(...)。我通过创建委托并使用方法调用器使其更简单,这样我可以将所有调用都变成一行代码。委托本质上创建了一个小方法,然后使用MethodInvoker调用该方法。但是,由于更改在UI线程上,因此不能从后台线程调用它们而不使用.Invoke,这是不允许的。
    您的调用方式可以按以下方式更改:
    richTextRxMessage.Invoke(new MethodInvoker(delegate { Text =  szData; }));
    

    该调用也可以直接从UI线程本身工作,因此您实际上不需要if语句,但从“成本”的角度来看,它比直接调用属性慢。

    当然,这只有在您一开始就处于后台线程时才会有所不同。 如果没有,则需要调查错误本身。 可能返回文本存在问题,请将其写入文件并查看它,看看是否可以发现问题。 尝试手动设置RTB的文本,看看它是否允许发生。 等等。


    你可以使用 dirtyImage.Invoke((MethodInvoker)delegate {Visible = RigX.IsDirty; Refresh();}); 的写法。此外,我个人会为那些需要多于一个命令的操作创建一个 ...Invoked 方法,因为这样你只需要检查/更改一段代码。 - Sinatr
    旧代码,现在我知道了...但是从未获得过回去重构它的许可。该死的业务规则和需要生产环境的权限 :) - Nevyn

    0
    问题可能是多线程相关的,正如MS论坛上建议的那样。由于您以异步方式请求此操作,因此它将创建另一个线程来处理输入,以便您的主应用程序线程不会被阻塞。
    要解决此问题,您将必须调用要更改其属性的控件。您需要这样做,因为您的主应用程序线程拥有UI,并且不能从另一个线程执行UI上的任何操作。
    调用不复杂,可以用几行代码完成。
    更改以下行:
    richTextRxMessage.Text =  szData;
    

    richTextRxMessage.Invoke((MethodInvoker) delegate {this.Text = szData;});
    

    这段代码将要求主UI线程更新文本,而不是自己完成。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接