Асинхронные компоненты и WinForms - PullRequest
3 голосов
/ 28 ноября 2011

В настоящее время я пишу компонент для связи с устройством на базе Ethernet, и мне приходится использовать асинхронные сокеты.Иногда, когда я получаю определенные «команды» от устройства, мне нужно вызвать событие для любой программы, использующей мой компонент (чаще всего 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», пользователь должен просто иметь возможность воздействовать на него, не задумываясь.

EDIT (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));
     }
}

Ответы [ 4 ]

2 голосов
/ 28 ноября 2011

Сделайте себе одолжение и положитесь на 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());
1 голос
/ 28 ноября 2011

Мне нравится ответ @ 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.
}
0 голосов
/ 28 ноября 2011

Это должно быть довольно легко решить: вам просто нужно выполнить любой код в вашей форме, который обновляет элементы управления с помощью Invoke.

Точная реализация будет зависеть от того, как асинхронный код вызывает вашу форму.Если вы добавите этот код к своему вопросу, мы можем предоставить более полный ответ.

0 голосов
/ 28 ноября 2011

Вы можете справиться с этим в 2 местах в зависимости от вашего дизайна. Если событие вызывается из другого потока, вы можете обработать его в обработчике событий, проверив свойство .invokeReqeuired формы (или другого элемента управления), обрабатывающей событие. Если он возвращает true, вы должны использовать метод .beginInvoke для перенаправления вызова в соответствующий поток.

В зависимости от вашего дизайна вы можете обработать его с другого конца, передав вашему компоненту экземпляр формы, которую вы хотите собрать. Прежде чем инициировать событие, проверьте .invokeRequired и упорядочите вызов, чтобы событие было инициировано в соответствующем потоке. Таким образом, код, использующий вашу библиотеку, не должен беспокоиться о потоках, но для этого необходимо, чтобы ваша библиотека имела ссылку на system.windows.forms.

...