异步组件和WinForms
本文关键字:WinForms 组件 异步 | 更新日期: 2023-09-27 18:15:59
我目前正在编写一个组件与基于以太网的设备通信,我不得不使用异步套接字。有时,当我从设备收到特定的"命令"时,我需要为使用我的组件的任何程序(通常是WinForm)引发一个事件。我正在为用户创建一个示例表单,但我有困难允许客户端表单接收事件和修改表单;我得到了典型的"跨线程操作无效:控制'listStrings'从线程访问,而不是创建它的线程。"
我试着阅读了《实现基于事件的异步模式》和《攻略:实现支持基于事件的异步模式的组件》,尽管这似乎不是我所需要的,尤其是在阅读第一个链接中的"实现基于事件的异步模式的机会"时。
。Net/c#更像是一种爱好而不是职业,在这个项目中——这是我在完成它之前需要弄清楚的最后一部分。使用一个"线程安全"(我知道,每个人都把这个术语当作它只意味着一件事)现有的TCP/IP组件,而不是尝试自己实现它会更好吗?
编辑:这里是我的网络类代码,向您展示我现在是如何实现它的。我忘记在哪里看到这个代码片段了,但是在我添加表单之前,它一直工作得很好。internal class Network
{
private Device dev;
private TcpClient client;
private NetworkStream ns;
private byte[] buffer = new byte[2048];
private Queue<byte[]> _msgQ = new Queue<byte[]>();
public Network(Device d)
{
dev = d;
}
internal void Connect(string ipAddress, int port)
{
client = new TcpClient();
client.BeginConnect(ipAddress, port, new AsyncCallback(OnConnect), null);
}
internal byte[] getLocalIp()
{
return ((IPEndPoint)client.Client.LocalEndPoint).Address.GetAddressBytes();
}
private void OnConnect(IAsyncResult ar)
{
try
{
client.EndConnect(ar);
ns = new NetworkStream(client.Client);
ns.BeginRead(buffer, 0, 2048, new AsyncCallback(OnRead), null);
while (_msgQ.Count > 0)
{
byte[] message = _msgQ.Dequeue();
ns.Write(message, 0, message.Length);
}
dev.dvDevice._connected = true;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
internal void Disconnect()
{
try
{
client.Close();
dev.dvDevice._connected = false;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
internal void Write(byte[] message)
{
if ((!client.Connected) || ns == null)
{
_msgQ.Enqueue(message);
return;
}
ns.Write(message, 0, message.Length);
}
private void OnWrite(IAsyncResult ar)
{
try
{
ns.EndWrite(ar);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private void OnRead(IAsyncResult ar)
{
try
{
int recv = ns.EndRead(ar);
byte[] message = new byte[recv];
Buffer.BlockCopy(buffer, 0, message, 0, recv);
dev.dvDevice._mh.Parse(message);
ns.BeginRead(buffer, 0, 2048, new AsyncCallback(OnRead), null);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Device
是向客户端公开的类。它包含一个MessageHandler
(_mh)类,它执行所有解析。Device
包含由MessageHandler
对特定响应进行调用的公共事件。希望这对我到目前为止学到的有帮助;我不喜欢重写太多,但为了使它正确(并正常工作),如果我必须的话,我会这样做。
编辑(2):我对这个库的目标是用户根本不应该管理任何线程——所以当一个事件被引发时,比如"ReceiveString",用户应该能够毫不犹豫地对它采取行动。
编辑(3):
public delegate void OnStringEvent(byte[] str);
public class Device
{
internal struct _device
{
// other stuff too, but here's what's important
public bool _connected;
public bool _online;
public MessageHandler _mh;
public Network _net;
}
public event OnStringEvent OnString;
internal void ReceiveString(byte[] str)
{
OnString(str);
}
internal _device dvDevice;
public Device(int device_number, int system_number)
{
dvDevice = new _device(device_number, system_number);
dvDevice._mh = new MessageHandler(this);
dvDevice._net = new Network(this);
}
}
internal class MessageHandler
{
private Device dev;
public MessageHandler(Device d)
{
dev = d;
}
public void Parse(byte[] message)
{
// The code goes through the message and does what it needs to
// and determines what to do next - sometimes write back or something else
// Eventually if it receives a specific command, it will do this:
dev.ReceiveString(ParseMessage(ref _reader));
}
}
帮你自己一个忙,让TPL为你完成同步提升吧。例子:
NetworkStream stream = MySocket.NetworkStream;
// creat a Task<int> returning the number of bytes read based on the Async patterned Begin- and EndRead methods of the Stream
Task<int> task = Task<int>.Factory.FromAsync(
fs.BeginRead, fs.EndRead, data, 0, data.Length, null);
// Add the continuation, which returns a Task<string>.
return task.ContinueWith((task) =>
{
if (task.IsFaulted)
{
ExceptionTextBox.Text = task.Exception.Message;
}
else
{
ResultTextBox.Text = string.Format("Read {0} bytes into data", task.Result);
}
}, TaskScheduler.FromCurrentSynchronizationContext());
我喜欢@Polity的回答,作为一个Rx的粉丝,我会说使用Rx (Reactive Extensions)
//we convert a typical begin/end (IAsyncPattern) into an observable sequence
//it returns a Func -read() that takes a byte, two ints and returns one.
var read = Observable.FromAsyncPattern<byte[], int, int, int>
(networkStream.BeginRead, networkStream.EndRead)
.ObserveOn(Scheduler.Dispatcher);
// Now, you can get an IObservable instead of an IAsyncResult when calling it.
byte[] someBytes = new byte[10];
IObservable<int> observable = read(someBytes, 0, 10);
observable.Subscribe(x=>
//x will be the returned int. You can touch UI from here.
);
根据你的代码,我可以看到另一个线程调用OnString
事件,然后我假设当你订阅它时,你只是将字符串添加到列表框中。
device.OnString += new OnStringEvent(device_onstring);
void device_onstring(byte[] str)
{
listStrings.Items.Add(...);//this is wrong, will give cross thread op error.
//you do this:
this.Invoke(new MethodInvoker(delegate()
{
listStrings.Items.Add(..);
//or anything else that touches UI
});
// this should refer to a form or control.
}
你可以根据你的设计在两个地方处理这个问题。如果事件是从另一个线程引发的,您可以通过检查处理事件的窗体(或其他控件)的. invokerequerequired属性在事件处理程序中处理它。如果它返回true,你应该使用. begininvoke方法将调用封送到正确的线程。
根据您的设计,您可以通过向组件传递您想要封送的表单实例来从另一端处理它。在引发事件之前,检查. invokerrequirequired并封送调用,以便在适当的线程中引发事件。这样,使用库的代码就不必担心线程,但它要求库有对system.windows.forms.
这应该是一个非常容易解决的问题:您只需要执行表单中使用Invoke更新控件的任何代码。
精确的实现将取决于异步代码如何回调到您的表单。如果您在问题中添加该代码,我们可以提供更完整的答案。