Stop Stream.BeginRead()

六月ゝ 毕业季﹏ 提交于 2019-12-01 19:35:13

You can try calling sp.DiscardOutBuffer(). It will call your read callback and you can then use stream.EndRead().

private void disconnectButton_Click(object sender, EventArgs e)
{
    sp.DiscardOutBuffer();
    stream.EndRead(recv_result);
    sp.Close();
}

You can try this program.

using System;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;
using System.IO;
using System.Text;

public class clsState {
    private const int BUFFER_SIZE = 1024;
    public byte[] BytesBuffer = new byte[BUFFER_SIZE];
    public SerialPort sp;
    public Stream stream;
}

public partial class Form1 : Form {

    SerialPort sp;
    Stream stream;
    IAsyncResult recv_result;

    bool endLoop = false;
    Thread _readThread;
    private ManualResetEvent _readDone = new ManualResetEvent(false);

    private void button1_Click(object sender, EventArgs e) {
        sp = new SerialPort("COM8", 9600);
        sp.Open();
        sp.ReadTimeout = 50000;
        sp.NewLine = "\n\r\0";
        stream = sp.BaseStream;

        // save serial port and stream to state object
        clsState state =  new clsState();
        state.sp = sp;
        state.stream = stream;

        // create worker thread
        endLoop = false;
        _readThread = new Thread(() => ReadThreadProc(state))
        _readThread.IsBackground = true;
        _readThread.Start();
    }

    private void ReadThreadProc(clsState state) {
        while (endLoop == false){
            // open and then close the gate as soon as after one thread passed
            _readDone.Reset();

            // starting ascynchronous read 
            recv_result = state.stream.BeginRead(state.BytesBuffer, 0, state.BytesBuffer.Length, new AsyncCallback(ReadCallBack), state.stream);

            // worker thread block in here (waiting for... _readDone.Set())
            _readDone.WaitOne();
        }
    }

    private void ReadCallBack(IAsyncResult ar) {   
        string temp;
        int bytesRead = 0;

        // read serial port and stream from IAsyncResult
        clsState state = (clsState) ar.AsyncState;

        // if port serial is open and..
        if (state.sp.IsOpen) {
            // the stream can read then..
            if(state.stream.CanRead) {
                // wait for asynchronous read to completed
                bytesRead = state.stream.EndRead(ar);
            }
        }

        if(bytesRead > 0) {
           // convert data in state.BytesBuffer from bytes array to string and save to temp variable
           temp = Encoding.ASCII.GetString(state.BytesBuffer);
            // open gate for next data
           _readDone.Set();
        }
    }

    private void disconnectButton_Click(object sender, EventArgs e) {
        // ending loop and will kill worker thread...
        endLoop = true;

        // release begin read
        _readDone.Set();      

        if (_readThread != null){
            if (_readThread.IsAlive){ // if worker thread still live
                _readThread.Join();   // wait thread off in here..
            }
        }

        // close serial port
        if (sp.IsOpen) sp.Close();

        // close stream and dispose it 
        if (stream.CanRead || stream.CanWrite) {
            stream.Close();
            stream.Dispose();
        }
    }
}
vhar19

It's an old post, but I think this solution can help people with the same problem.

I was using legacy code for an application and I found that the problem with BeginRead and EndRead is that there is no way to cancel the asynchronous operation. Therefore, when you close the port, your call to BeginRead stays there forever until another byte is received in the port, then your call to EndRead will free up the port. If it does not happen this way, then your application may hang and not even task manager can close it until you unplug the serial port cable!

Fortunately the TPL library can fix this problem in a very simple and elegant way. The CancelToken is what you need:

On port open:

while (x)   
   var myTask = _serialPort.BaseStream.ReadAsync(_serialBuffer, 0, _bufferLength, cancelToken.Token);
   var bytesRead = await myTask;
   ... your business logic here... 
    if ((myTask.IsCanceled) || (myTask.IsFaulted)) break;
}

On port close:

cancelToken.Cancel(false);

Please note a while loop is better than recursive call because when port is broadcasting lots of information, a stack overflow exception is thrown after 15 minutes.

It's a very simple implementation. I hope it helps

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!