성능이 저하되고 끔찍한 직렬 포트 코드를 어떻게 개선할 수 있습니까?

StackOverflow https://stackoverflow.com/questions/1233958

문제

매우 불안정한 보기 흉한 직렬 포트 코드 조각이 있습니다.

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);


       var response = dataCollector.Collect(buffer);

       if (response != null)
       {
           this.OnDataReceived(response);
       }

       Thread.Sleep(100);
    }    
}

Thread.Sleep(100) 호출 중 하나를 제거하면 코드 작동이 중지됩니다.

물론 이것은 실제로 물건을 느리게하고 많은 데이터 스트림이 들어 오면 수면을 더 크게 만들지 않는 한 작동을 중단합니다.(순수한 교착 상태처럼 작동이 중지됩니다)

DataEncapsular 및 데이터 수집기는 MEF가 제공하는 구성 요소이지만 성능은 상당히 좋습니다.

클래스에는 Listen () 메소드가있어 배경 작업자가 데이터를 수신하기 위해 시작합니다.

public void Listen(IDataCollector dataCollector)
{
    this.dataCollector = dataCollector;
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerAsync();
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    port = new SerialPort();

    //Event handlers
    port.ReceivedBytesThreshold = 15;
    port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);

    ..... remainder of code ...

제안을 환영합니다!

업데이트:*IDataCollector 클래스의 기능에 대해 간단히 설명합니다.전송 된 데이터의 모든 바이트가 단일 읽기 작업에서 읽는 지 알 수있는 방법이 없습니다.따라서 데이터를 읽을 때마다 데이터를 읽습니다. 데이터는 완전하고 유효한 프로토콜 메시지가 수신되면 True를 반환합니다.이 경우 여기서는 동기화 바이트, 길이, CRC 및 테일 바이트를 확인합니다.실제 작업은 나중에 다른 수업에 의해 이루어집니다.*

업데이트 2:이제 제안된 대로 코드를 교체했지만 여전히 문제가 있습니다.

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);

        var response = dataCollector.Collect(buffer);

        if (response != null)
        {
            this.OnDataReceived(response);
        }     
}

빠르고 안정적인 연결로 제대로 작동하는 것을 볼 수 있습니다.그러나 OnDataReceived는 데이터가 수신될 때마다 호출되지 않습니다.(자세한 내용은 MSDN 문서를 참조하세요.)따라서 데이터가 조각화되고 이벤트 내에서 한 번만 읽으면 데이터가 손실됩니다.

그리고 지금 나는 왜 처음부터 루프를 가지고 있었는지 기억합니다. 왜냐하면 연결이 느리거나 불안정한 경우 실제로 여러 번 읽어야하기 때문입니다.

당연히 while 루프 솔루션으로 돌아갈 수 없습니다. 그러면 어떻게 해야 합니까?

도움이 되었습니까?

해결책

원래 while 기반 코드 조각에 대한 첫 번째 관심사는 바이트 버퍼에 대한 메모리의 지속적인 할당입니다.여기에 특별히 .NET 메모리 관리자로 이동하여 버퍼용 메모리를 할당하는 동시에 마지막 반복에서 할당된 메모리를 가져와 최종 가비지 수집을 위해 사용되지 않은 풀로 다시 보내는 "새" 문을 추가합니다.비교적 빡빡한 루프에서 수행해야 할 작업이 엄청나게 많은 것 같습니다.

디자인 타임에 합리적인 크기(예: 8K)로 이 버퍼를 생성하여 이러한 메모리 할당, 할당 해제 및 조각화를 모두 수행하지 않으면 얻을 수 있는 성능 향상이 궁금합니다.그게 도움이 될까요?

private byte[] buffer = new byte[8192];

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        var read = port.Read(buffer, 0, count);

        // ... more code   
    }
}

루프를 반복할 때마다 이 버퍼를 재할당할 때 고려해야 할 또 다른 점은 버퍼가 이미 충분히 크면 재할당이 불필요할 수 있다는 점입니다.다음을 고려하세요:

  • 루프 반복 1:100바이트 수신됨;100바이트의 버퍼 할당
  • 루프 반복 2:75바이트 수신됨;75바이트의 버퍼 할당

이 시나리오에서는 루프 반복 1에 할당된 100바이트의 버퍼가 루프 반복 2에서 수신된 75바이트를 처리하기에 충분하기 때문에 실제로 버퍼를 다시 할당할 필요가 없습니다.100바이트 버퍼를 삭제하고 75바이트 버퍼를 생성할 필요가 없습니다.(물론 정적으로 버퍼를 생성하고 루프 밖으로 완전히 이동하는 경우에는 문제가 됩니다.)

또 다른 측면에서는 DataReceived 루프가 데이터 수신에만 관련되어 있다고 제안할 수 있습니다.해당 MEF 구성 요소가 무엇을 하는지는 잘 모르겠지만 해당 작업이 데이터 수신 루프에서 수행되어야 하는지 궁금합니다.수신된 데이터를 일종의 대기열에 넣고 MEF 구성 요소가 해당 데이터를 선택할 수 있습니까?DataReceived 루프를 가능한 한 빠르게 유지하고 싶습니다.아마도 수신된 데이터를 큐에 넣어서 더 많은 데이터를 수신하는 작업으로 바로 돌아갈 수 있을 것입니다.다른 스레드를 설정하여 큐에 도착하는 데이터를 감시하고 MEF 구성 요소가 거기에서 데이터를 선택하여 작업을 수행하도록 할 수 있습니다.이는 더 많은 코딩이 필요할 수 있지만 데이터 수신 루프가 최대한 응답하도록 도울 수 있습니다.

다른 팁

그리고 너무 간단할 수도 있는데...

DataReceived 핸들러를 사용하지만 루프나 Sleep() 없이 사용하거나 준비된 데이터를 읽고 어딘가(큐 또는 MemoryStream)로 푸시합니다.

또는

스레드(BgWorker)를 시작하고 serialPort1.Read(...)를 (차단) 수행한 다음 얻은 데이터를 다시 푸시하거나 어셈블합니다.

편집하다:

당신이 게시한 내용에 따르면 다음과 같습니다.이벤트 핸들러를 삭제하고 Dowork() 내부의 바이트를 읽으십시오.이는 ReadBufferSize보다 훨씬 작은 한 원하는 데이터의 양을 지정할 수 있다는 이점이 있습니다.

Update2에 관한 Edit2:

이벤트를 전혀 사용하지 않고 BgWorker 내부에서 while 루프를 사용하는 것이 훨씬 더 좋습니다.간단한 방법:

byte[] buffer = new byte[128];  // 128 = (average) size of a record
while(port.IsOpen && ! worker.CancelationPending)
{
   int count = port.Read(buffer, 0, 128);
   // proccess count bytes

}

이제 레코드의 크기가 가변적이어서 레코드를 완료하기 위해 다음 126바이트가 들어올 때까지 기다리고 싶지 않을 수도 있습니다.버퍼 크기를 줄이거나 ReadTimeOut을 설정하여 이를 조정할 수 있습니다.매우 세분화하려면 port.ReadByte()를 사용할 수 있습니다.ReadBuffer에서 읽기 때문에 실제로는 느리지 않습니다.

파일에 데이터를 쓰고 싶은데 직렬 포트가 너무 자주 멈추는 경우 이 방법을 사용하면 됩니다.가능하다면 단일 파일에 넣으려는 모든 바이트를 담을 수 있을 만큼 버퍼를 크게 만드십시오.그런 다음 아래와 같이 datareceived 이벤트 핸들러에 코드를 작성합니다.그런 다음 기회가 생기면 아래와 같이 전체 버퍼를 파일에 쓰십시오.직렬 포트가 버퍼를 읽는 동안 버퍼에서 읽어야 하는 경우 교착 상태 및 경쟁 조건을 피하기 위해 버퍼링된 스트림 개체를 사용해 보십시오.

private byte[] buffer = new byte[8192]; 
var index = 0;
void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{    
    index += port.Read(buffer, index, port.BytesToRead);
} 

void WriteDataToFile()
{
    binaryWriter.Write(buffer, 0, index); 
    index = 0;
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top