Ukončení asynchronní operace   zodpovězená otázka

VB.NET

Zdravím,

už asi rok používám úspěšně kód, který jsem kdysi odkoukal ze známého příkladu Kreslící tabule a měl ho původně v jednoduché konzolové aplikaci. Jeho úkolem je asynchronně číst ze sériového portu, došlá data rozparsovat a posílat dál pomocí UDP. Teď jsem to chtěl přepsat a zároveň z toho udělat jednoduchou formulářovou aplikaci, aby mohla obsluha nastavit pár parametrů, když to bude potřeba. Narazil jsem na problém, že neumím nijak ukončit asynchronní čtení, které zahajuju příkazem

SerialPort.BaseStream.BeginRead(mBuffer, 0, mBufferSize, acCtiDataCom, Nothing) 'Začínám asynchronně číst

acCtiDataCom vypadá takto:

Private acCtiDataCom As New AsyncCallback(AddressOf CtiDataCom)
Private Sub CtiDataCom(ByVal at As System.IAsyncResult)
  'Asynchronní čtení dat
  Try
    Dim prijato As Integer = SerialPort.BaseStream.EndRead(at) 'Dokončím čtení dat a zjistím kolik dat přišlo
    If prijato < 1 Then Throw New Exception() 'Spojení bylo ukončeno (přichází 0B dat)
    mByteBuffer.Append(mBuffer, prijato) 'Zkopíruji došlá data na konec pracovního bufferu
    RozdelNaZpravy(prijato) 'Pošlu k parsování
    SerialPort.BaseStream.BeginRead(mBuffer, 0, mBufferSize, acCtiDataCom, Nothing)     'Přečetl jsem všechna příchozí data, proto budu čekat až přijdou další
    Catch e As ObjectDisposedException
     'Obsluha chyby
    End Try
  End Sub

Jde mi o to, že chci umožnit přerušení načítání a uvolnění sériového portu a přitom nechat aplikaci spuštěnou. Zkoušel jsem metody Close na objektu SerialPort i na podkladovém Streamu i všechny možné další, ale vždycky po jejich zavolání mně program vletí do procedury CtiDataCom a nahlásí chybu na příkazu

SerialPort.BaseStream.EndRead(at)

Díky předem za případné rady

nahlásit spamnahlásit spam 0 odpovědětodpovědět

K jaké vyjímce s jakou zprávou dojde?

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Na řádku

 Dim prijato As Integer = SerialPort.BaseStream.EndRead(at)

dojde k vyjímce

System.NullReferenceException was unhandled

Message="Object reference not set to an instance of an object."

Source="CasomiraGPrenos"

StackTrace:

at SerialCom2.CtiDataCom(IAsyncResult at) in D:\Bin\Komunikace.vb:line 869

at System.IO.Ports.SerialStream.AsyncFSCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOverlapped)

at System.Threading._IOCompletionCallback.IOCompletionCallback_Context(Object state)

at System.Threading.ExecutionContext.runTryCode(Object userData)

at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)

at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)

protože Objekt SerialPort, který je součástí třídy SerialCom2, už v tu chvíli má hodnotu Nothing.

Pokud objekt SerialPort nenastavím na Nothing, ale pouze uzavřu port příkazem SerialPort.Close(), objeví se odpovídající vyjímka:

System.InvalidOperationException was unhandled

Message="The BaseStream is only available when the port is open."

Source="System"

StackTrace:

at System.IO.Ports.SerialPort.get_BaseStream()

at SerialCom2.CtiDataCom(IAsyncResult at) in D:\Bin\Komunikace.vb:line 869

at System.IO.Ports.SerialStream.AsyncFSCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOverlapped)

at System.Threading._IOCompletionCallback.IOCompletionCallback_Context(Object state)

at System.Threading.ExecutionContext.runTryCode(Object userData)

at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)

at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)

Vysvětluju si to laicky (a možná špatně) tak, že příkazem

SerialPort.BaseStream.BeginRead(mBuffer, 0, mBufferSize, acCtiDataCom, Nothing)

vytvořím samostatné vlákno, které žije vlastním životem. Jen mě mate, že program do procedury acCtiDataCom vletí i když na sériový port nepřijde žádný znak.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Omlouvám se, zapomněl jsem se přihlásit, předchozí příspěvek je ode mne.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Vy vůbec nechápete podstatu asynchronního zpracování pomocí metod BeginNěco EndNěco (je toho plný Framework, nejen streamy). Sama metoda BeginRead už automaticky vytvoří nové vlákno, ve kterém zpracování poběží a na dokončení se počká pomocí EndRead. Což je přesně v rozporu s kódem, který uvádíte a kde je EndRead jako první (tudíž musí dojít k vyjímce, protože žádná asynchronní operace ještě nebyla zahájena a proto IAsyncResult je Nothing).

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Je mi jasné, že aby mohlo něco skončit, tak to musí nejdřív začít.

Už v prvním příspěvku jsem psal, že asynchronní čtení zahajuji pomocí BeginRead (neuvedl jsem jen, že se tak děje v samotném konstruktoru objektu SerialCom2, který to celé obaluje)

Též jsem uváděl, že metoda BeginRead vytváří nové vlákno a mně jde právě o to, jak toto vlákno korektně předčasně ukončit. V okamžiku vyvolání vyjímky není IAsuncResult Nothing. Nothing je v tu chvíli buď celý objekt SerialCom2, pokud na něj zavolám Dispose, nebo, pokud pouze uzavřu Sériový port (SerialPort.Close), pak to hlásí tu druhou vyjímku.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Obávám se, že to co běží ve vlákně vytvořeném a spuštěném metodou BeginRead přerušit nepůjde. Z kódu není poznat, v jaké chvíli BeginRead spouštíte, je to na událost DataReceived?

nahlásit spamnahlásit spam 1 / 1 odpovědětodpovědět

Princip je téměř doslova převzatý z příkladu Kreslící tabule z tohoto webu, akorát s úpravou na sériový port.

BeginRead volám poprvé při vlastní inicializaci objektu, (v ní samozřejmě před tím otevřu objekt pro sériový port) a potom vždy na konci vlastní obslužné procedury acCtiDataCom, která je sama o sobě vždy parametrem callback této metody BeginRead.

Asi by to chtělo spouštět BeginRead pouze na událost DataReceived.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Tak jsem přesunul volání BeginRead do událostní procedury volané při DataReceived a je to v pohodě.

Dík.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.
  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback