AsyncCallback和线程 [英] AsyncCallback and Threadings

查看:96
本文介绍了AsyncCallback和线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在这段代码中没有使用任何线程,但是我有这个
跨线程操作无效:从创建该线程的线程之外的其他线程访问控件"txtStatus"."
当我单击表单上的连接"按钮时,请帮助我,谢谢您

Hi ,I didnt use any thread in this code but I have this
"Cross-thread operation not valid: Control ''txtStatus'' accessed from a thread other than the thread it was created on."
when I click the "Connect" button on the form,please help me,thank you

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;

namespace ExampleNetworkAsyncTCPClient
{
    public partial class Form1 : Form
    {
        private Socket client;
        private byte[] data = new byte[1024];
        private int size = 1024;
        void Connected(IAsyncResult iar)
        {
            client = (Socket)iar.AsyncState;
            try
            {
                client.EndConnect(iar);
                txtStatus.Text = "Connected to " + client.RemoteEndPoint.ToString();
            }
            catch (SocketException)
            {
                txtStatus.Text = "Error connecting";
            }
        }
        void ReceiveData(IAsyncResult iar)
        {
            Socket remote = (Socket)iar.AsyncState;
            int rcv = remote.EndReceive(iar);
            string stringData = Encoding.ASCII.GetString(data, 0, rcv);
            lstList.Items.Add(stringData);
        }
        void SendData(IAsyncResult iar)
        {
            Socket remote = (Socket)iar.AsyncState;
            int sent = remote.EndSend(iar);
            remote.BeginReceive(data, 0, size, SocketFlags.None, new AsyncCallback(ReceiveData), remote);
        }
        public Form1()
        {
            InitializeComponent();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            client.Close();
            txtStatus.Text = "Disconnected";
        }

        private void cmdConnect_Click(object sender, EventArgs e)
        {
            txtStatus.Text = "Connecting...";
            Socket newSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9050);
            newSock.BeginConnect(ipep, new AsyncCallback(Connected), newSock);

        }

        private void cmdSend_Click(object sender, EventArgs e)
        {
            byte[] message = Encoding.ASCII.GetBytes(txtMessage.Text);
            txtMessage.Clear();
            client.BeginSend(message, 0, message.Length, SocketFlags.None, new AsyncCallback(SendData), client);
            

        }
    }
}

推荐答案

这里有两堆问题.首先,为什么要使用任何异步API?让我建议您:甚至不要玩与网络打交道而不使用线程的想法.您需要在单独的线程中使用任何阻止操作,包括网络.现在,我认为在线程不常见的情况下提供了异步API.无论如何,它都使用引擎盖后面的螺纹,但是没有您的控制.如果您使用自己的线程,则它更简单,易于实现和维护.

第二个问题是您尝试通过某些非UI线程对UI进行操作.如果您确实确实需要以某种方式使用UI,该如何解决此问题?方法如下:

您无法从非UI线程调用与UI相关的任何操作.相反,您需要使用System.Windows.Threading.DispatcherInvokeBeginInvoke方法(对于Forms或WPF)或System.Windows.Forms.Control(仅对于Forms).

在我过去的答案中,您将找到有关其工作原理的详细说明和代码示例:
Control.Invoke()与Control.BeginInvoke() [ ^ ],
Treeview扫描仪和MD5的问题 [如何获取keydown事件在vb.net中的不同线程上操作 [启用禁用+多线程后控件事件不会触发 [ ^ ].



我提供了完整的代码示例,显示了我在上面尝试提到的线程和调用技术.请参阅我的其他答案.

另外,还有一个关于网络的建议:我建议不要使用较低级别的Socket类,而应该使用更方便的类System.Net.Sockets.TcpListenerSystem.Net.Sockets.TcpClient,请参见:
http://msdn.microsoft.com/en-us/library/system. net.sockets.tcplistener.aspx [ ^ ],
http://msdn.microsoft.com/en-us/library/system. net.sockets.tcpclient.aspx [ ^ ].

请同时参阅我过去在该主题上的答案,以获取有关在网络中使用线程的一些有用的想法:
There are two bunches of problems here. First, why using any asynchronous API? Let me advise you: don''t even play with the idea of dealing with network and not using threading. You need to use any blocking operations including network in a separate thread. Now, I think that asynchronous API was offered when threading was not a common place. It uses thread behind the hood anyway, but without your control. If you are using your own thread, it''s way more straightforward, easy to implement and maintain.

The second problem is that you tried to do something with the UI from some non-UI thread. How to solve this problem if you really need to work with UI somehow? Here is how:

You cannot call anything related to UI from non-UI thread. Instead, you need to use the method Invoke or BeginInvoke of System.Windows.Threading.Dispatcher (for both Forms or WPF) or System.Windows.Forms.Control (Forms only).

You will find detailed explanation of how it works and code samples in my past answers:
Control.Invoke() vs. Control.BeginInvoke()[^],
Problem with Treeview Scanner And MD5[^].

See also more references on threading:
How to get a keydown event to operate on a different thread in vb.net[^],
Control events not firing after enable disable + multithreading[^].



I provided a full code sample showing the threading and invocation techniques I tried to mention above. Please see my other answer.

Also, a bonus piece of advice of networking: I would recommend to use not the lower-level Socket class, but more convenient classes System.Net.Sockets.TcpListener and System.Net.Sockets.TcpClient, please see:
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.aspx[^],
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.aspx[^].

Please also see my past answer on this topic for some useful ideas on using threads with networking: Multple clients from same port Number[^].

—SA


这是根据OP的请求创建的代码示例:



首先,让我们做一个线程包装器.稍后,您可以添加一些随机算法,并在其中输入数字范围.现在,重要的是要制作骨架:生成一些数字并以新值触发事件;同样,它可以被启动/暂停/恢复/中止.线程本身对用户是隐藏的.注意事件句柄的使用-这是使用长寿命线程并将其保持在等待状态而又不浪费任何CPU时间的方法.等待句柄时,线程将被切出,并且永远不会调度回执行,直到被其他任何线程中的ManualResetEvent.Set,超时或Abort调用唤醒.

该线程是从UI中抽象出来的. UI线程负责将事件句柄添加到事件NumberGenerated的调用列表中.
线程包装器对于避免参数化的线程启动以及总体而言更好的封装非常重要.

请查看我过去的答案以获取更多详细信息:
如何将ref参数传递给线程 [ ^ ],
启动后更改线程(生产者)的参数 [ ^ ].

This is a code sample created on OP''s request:



First, let''s make a thread wrapper. Later on, you can add some random algorithm, input numeric ranges in it. For now, it''s important to make a skeleton: generate some number and fire an event with new value; also, it can be started/paused/resumed/aborted. The thread itself is hidden for a user. Note the use of event handle — this is the way to use long-living thread and keep it in a wait state without wasting any CPU time. On a wait of the handle, a thread is switched out and never scheduled back to execution until waken up by the call to ManualResetEvent.Set, timeout or Abort called in any other thread.

The thread is abstracted from the UI. It''s responsibility of the UI thread to add an event handle to the invocation list of event NumberGenerated.

The thread wrapper is very important to avoid parametrized thread start and for better encapsulation in general.

Please see my past answers for more detail:
How to pass ref parameter to the thread[^],
change paramters of thread (producer) after it started[^].

namespace RandomTest {
    using System.Threading;
    
    internal class ThreadWrapper {

        internal class NumberGeneratedEventArgs : System.EventArgs {
            internal NumberGeneratedEventArgs(int number) { this.fNumber = number; }
            internal int Number { get { return fNumber; } }
            int fNumber;
        } //class NumberGeneratedEventArgs
        
        internal ThreadWrapper(int sleepTime) {
            this.thread = new Thread(this.Body);
            this.sleepTime = sleepTime;
        } //ThreadWrapper

        internal void Start() { this.thread.Start(); }
        internal void Abort() { this.thread.Abort(); }
        internal void Pause() { this.waitHandle.Reset(); }
        internal void Resume() { this.waitHandle.Set(); }

        internal event System.EventHandler<NumberGeneratedEventArgs>
            NumberGenerated;

        void Body() {
            int value = 0;
            while (true) {
                waitHandle.WaitOne();
                if (NumberGenerated != null)
                    NumberGenerated.Invoke(this, new NumberGeneratedEventArgs(value));
                value++;
                Thread.Sleep(sleepTime);
            } //loop
        } //Body
        
        Thread thread;
        int sleepTime;
        ManualResetEvent waitHandle = new ManualResetEvent(false); //non-signalled
    
    } //ThreadWrapper

} //namespace RandomTest



现在,让我们以表格形式使用它.我故意将其全部放在一个文件中,并避免使用Designer,因此所有代码都放在一个位置:



Now, let''s use it in the form. I intentionally made it all in one file and avoided use of Designer, so all code would be in one place:

namespace RandomTest {
    using System.Windows.Forms;

    public partial class FormMain : Form {

        const string ButtonStop = "&Stop";
        const string ButtonStart = "&Start";
        const int SleepTime = 500;

        delegate void NumberAction(Label label, int value);
        //in .NET version above 2.0 this line is not needed
        //use System.Action<Label, int> instead

        public FormMain() {
            Padding = new Padding(10);
            Button button = new Button();
            button.Text = ButtonStart;
            button.Dock = DockStyle.Bottom;
            Controls.Add(button);
            Label output = new Label();
            output.Dock = DockStyle.Fill;
            output.AutoSize = false;
            Controls.Add(output);
            button.Click += delegate(object sender, System.EventArgs eventArgs) {
                if (running) {
                    button.Text = ButtonStart;
                    wrapper.Pause();
                } else {
                    button.Text = ButtonStop;
                    wrapper.Resume();
                } //if
                running = !running;
            }; //button.click 
            wrapper.NumberGenerated += delegate(object sender, ThreadWrapper.NumberGeneratedEventArgs eventArgs) {
                output.Invoke(new NumberAction(delegate(Label label, int value) {
                    label.Text = value.ToString();
                }), output, eventArgs.Number);
            }; //wrapper.NumberGenerated
            wrapper.Start();
            this.Closing += delegate(object sender, System.ComponentModel.CancelEventArgs e) {
                wrapper.Abort();
            };
        } //FormMain

        bool running;
        ThreadWrapper wrapper = new ThreadWrapper(SleepTime);

    } //class FormMain

} //namespace RandomTest



我还尝试使用与仍在使用的C#v.2兼容的语法.在更高版本中,匿名委托的lambda语法非常有用.方法如下:



I also tried to use the syntax compatible with C# v.2, which is still used. In later versions, lambda syntax of anonymous delegates is highly beneficial. Here is how:

button.Click += (sender, eventArgs) => { /* ... */ };


这种语法有很多好处.首先,看一下处理程序:在这种情况下,您不需要使用参数.使用lambda语法,您甚至不需要查找参数类型-编译器从事件Click的类型推断出它们.即使您需要使用一个或两个参数,Intellisense也会向您显示可以使用的那些类型的成员,但是您仍然不需要记住或找出参数名称.

—SA


There are many benefits of this syntax. First of all, look at the handler: you don''t need to use the parameters in this case. With lambda syntax, you don''t even need to look for parameters types — they are inferred by a compiler from the type of the event Click. Even when you need to use one or both parameters, Intellisense will show you the members of those types you can use, but you still won''t need to remember or find out parameter names.

—SA


这篇关于AsyncCallback和线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆