Alright. So I've went ahead and rewrote most of the code, according to the articles I was pointed to.
It looks like this:
Progress<string, string> progressIndicator;
public void ShowTEF()
{
progressIndicator = new Progress<(string body, string title)>(AtualizaUI);
ComunicaComTEF(progressIndicator);
}
private async Task<int> ComunicaComTEF(IProgress<(string body, string title)> progress)
{
int retorno = 10000;
return await Task.Run<int>(() =>
{
while (retorno == 10000)
{
if (estadoTEF != StateTEF.OperacaoPadrao && estadoTEF != StateTEF.RetornaMenuAnterior)
{
Debug.WriteLine("estadoTEF != OperacaoPadrao. Awaiting response");
return 0;
}
else
{
Debug.WriteLine("estadoTEF == OperacaoPadrao");
retorno = ContinuaVendaTEF();
}
if (progress != null)
progress.Report((mensagemJanela, tituloJanela));
}
if (retorno < 0) this.Dispatcher.Invoke(() => DialogBox.Show("ERRO DE TEF", DialogBox.DialogBoxButtons.No, DialogBox.DialogBoxIcons.Error, true, "Erro!"));
if (statusAtual != StatusTEF.Confirmado) statusAtual = StatusTEF.Erro;
Debug.WriteLine("Closing window due to loop ending");
this.Dispatcher.Invoke(() => this.Close());
StatusChanged?.Invoke(this, new TEFEventArgs() { TipoDoTEF = _tipoTEF, Valor = valor, idMetodo = _idMetodo, status = statusAtual });
return 0;
});
}
private int ContinuaVendaTEF()
{
Debug.WriteLine(Encoding.ASCII.GetString(bufferTEF).Split('\0')[0], 0);
var retorno = ContinuaFuncaoSiTefInterativo(ref Comando, ref TipoCampo, ref TamMinimo, ref TamMaximo, bufferTEF, bufferTEF.Length, 0);
ProcessaComando(Comando, bufferTEF);
LimpaBuffer();
return retorno;
}
ProcessaComando
is a switch that, depending on comando
does something, like showing a message
private void ExibeMensagemOperador(byte[] buffer)
{
tituloJanela = "OPERAÇÃO NO TEF";
mensagemJanela = Encoding.ASCII.GetString(buffer).Split('\0')[0];
}
Or asking the user to press any key
public void PerguntaSimOuNao(byte[] pergunta)
{
estadoTEF = StateTEF.AguardaSimNao;
mensagemJanela = "(S)im / (N)ão";
tituloJanela = Encoding.ASCII.GetString(pergunta).Split('\0')[0];
}
Which is then captured by a PreviewTextInput
private void Window_PreviewTextInput(object sender, TextCompositionEventArgs e)
if (estadoTEF == StateTEF.AguardaSimNao && (e.Text.ToUpper() == "S" || e.Text.ToUpper() == "N"))
{
LimpaBuffer();
if (e.Text.ToUpper() == "S")
{
bufferTEF = Encoding.ASCII.GetBytes("0");
estadoTEF = StateTEF.OperacaoPadrao;
ComunicaComTEF(progressIndicator);
}
else if (e.Text.ToUpper() == "N")
{
bufferTEF = Encoding.ASCII.GetBytes("1");
estadoTEF = StateTEF.OperacaoPadrao;
ComunicaComTEF(progressIndicator);
}
}
Now, for the new information. When I run it using Task, with NO async/await, just returning a Task and its result synchronously triggers the FatalExecutionError. If ComunicaComTef
to int, and remove the Task.Run
(just running the code synchronously), the error is not triggered, and the loop runs flawlessly.
Previous version of the question, if needed:
I've been learning async programming for the last few months, and I've found an error I don't know how to debug/handle:
Here's the setup. I have a window
ShowTEF
, which calls two methods,IniciaFuncaoSitef
andasync ComunicaComTEF
. Both of them call external dll methods, which return integer values and a byte[] by ref.
IniciaFuncaoSitef
simply starts an operation, by providing some parameters to the external dll.ComunicaComTEF
has awhile
loop, that, for each sync call for the external method calls athis.Dispatcher.Invoke()
to refresh the UI. Here's the simplified code:
public void ShowTEF(TipoTEF tipoTEF, decimal vlrTEF) { Topmost = true; InitializeComponent(); Show(); IniciaFuncaoSiTefInterativo((int)tipoTEF, (vlrTEF*100).ToString("0.00")); //Starts a new interation with the external DLL. stateTEF=StateTEF.OperacaoPadrao; //Allows the while loop on ComunicaComTEF to run statusTEF = StatusTEF.EmAndamento; //This will be used by ShowTEF's caller to know what was the outcome of the operation. ComunicaComTEF(); } private async void ComunicaComTEF() { int retorno = 10000; await Task.Run(() => { while (retorno == 10000) //The external DLL returns 10000 as long as it needs my software to keep communicating with it. { if (stateTEF != StateTEF.CancelamentoRequisitado) //If there still stuff to do, and the user hasn't cancelled, the loop falls here. { if (stateTEF != StateTEF.OperacaoPadrao) //If the DLL asked some user interaction, the loop falls here. { this.Dispatcher.Invoke(() => AtualizaUI()); return; } else //If the DLL is still "chatting" with my software, the loop goes on. { retorno = ContinuaVendaTEF().intRetorno; this.Dispatcher.Invoke(() => AtualizaUI()); } } else //If the user presses Escape at any time, it will fall here at the next loop. { statusTEF = StatusTEF.Cancelado; retorno = CancelaOperacaoAtual(); this.Dispatcher.Invoke(() => this.Close()); return; } } string msgErro = retorno switch //These are actual error messages I've shortened here to save space { -1 => "ERRMESS1", -3 => "ERRMESS3", -4 => "ERRMESS4", -5 => "ERRMESS5", -8 => "ERRMESS8", -9 => "ERRMESS9", -10 => "ERRMESS10", -12 => "ERRMESS12", -20 => "ERRMESS20", -40 => "ERRMESS40", _ => "NAE" //Not an Error }; if (msgErro != "NAE") this.Dispatcher.Invoke(() => DialogBox.Show((msgErro)); //DialogBox inherits Window but has some custom parameters, like custom icons and custom buttons. if (statusTEF != StatusTEF.Confirmado) statusTEF = StatusTEF.Erro; //If, when the loop ends when return != 10000, the status is not confirmed, it understands there has been an error. this.Dispatcher.Invoke(() => this.Close()); //Closes the current window. StatusChanged?.Invoke(this, new TEFEventArgs() { TipoDoTEF = _tipoTEF, Valor = valor, idMetodo = _idMetodo, status = statusTEF }); //Alerts whoever called ShowTEF about the new status. return; }); } private (int intRetorno, string msgRetorno) ContinuaVendaTEF() { int retorno = ContinuaFuncaoSiTefInterativo(ref Comando, ref TipoCampo, bufferTEF, bufferTEF.Length); ProcessaComando(bufferTEF, bufferTEF.Length); ClearBuffer(); return (retorno, "NORETURN"); } private void Window_PreviewTextInput(object sender, TextCompositionEventArgs e) { if (stateTEF == StateTEF.AguardaMenu && e.Text.IsNumbersOnly()) { int opcaoEscolhida = int.Parse(e.Text); ClearBuffer(); bufferTEF = Encoding.UTF8.GetBytes(opcaoEscolhida.ToString()); stateTEF = StateTEF.OperacaoPadrao; ComunicaComTEF(); } else if (stateTEF == StateTEF.AguardaSimNao && (e.Text.ToUpper() == "S" || e.Text.ToUpper() == "N")) { ClearBuffer(); if (e.Text.ToUpper() == "S") { bufferTEF = Encoding.UTF8.GetBytes("0"); } else if (e.Text.ToUpper() == "N") { bufferTEF = Encoding.UTF8.GetBytes("1"); } stateTEF = StateTEF.OperacaoPadrao; ComunicaComTEF(); } ``` `IniciaFuncaoSiTefInterativo` and `ContinuaFuncaoSiTefInterativo` are the external methods imported using a DllImport with StdCall convention. `ProcessaComando` reads `Comando`, `TipoCampo` and `bufferTEF` and changes `stateTEF` to a different state from `OperacaoPadrao` so that the loop is broken and the user has to interact with the software. There is a `Window_KeyDown` and `Window_PreviewTextInput` that captures keystrokes as long as stateTEF is not OperacaoPadrao, processes it (storing the appropriate result in bufferTEF) and calls `ComunicaComTEF` back again. ---------- So that's it for the code. Now the issue. Sometimes the process runs flawlessly, but sometimes I get the following error: > Managed Debugging Assistant 'FatalExecutionEngineError' has detected a problem in 'M:\TrilhaWorkSpace\AmbiPDV-NFVenda\PDV_PRINCIPAL\bin\AnyCPU\Debug\AmbiPDV.exe'. Additional Information: The runtime has encountered a fatal error. The address of the error was at 0xf5b029e1, on thread 0x72bc. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack. I've tried enabling Managed Compatibility Mode (https://stackguides.com/questions/56846918/keyvaultclient-throws-fatalexecutionengineerror-on-debug-randomly), but I still get the same error. I've also tried disabling Diagnostics Tools when debugging. Any hints on how should I tackle this issue? I can provide any further info required, of course. ---------- EDIT.: Here's the Call Stack > [Managed to Native Transition] WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame = {System.Windows.Threading.DispatcherFrame}) + 0xbb bytes WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x4d bytes PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x60 bytes PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x7a bytes PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x2e bytes PresentationFramework.dll!System.Windows.Application.Run() + 0x1e bytes AmbiPDV.exe!PDV_WPF.App.Main() + 0x5a bytes ---------- EDIT 04/02/2020 As per @PanagiotisKanavos, I've adopted IProgress to better update my interface to show information (and request it) from the user. ``` public async Task ShowTEF(TipoTEF tipoTEF, decimal vlrTEF) { ... //ComunicaComTEF(); var progressIndicator = new Progress<(string, string)>(AtualizaUI); await ComunicaComTEF(progressIndicator); } private async Task ComunicaComTEF(IProgress<(string, string)> progress) { await Task.Run(() => { while (retorno == 10000) { progress.Report((message, title)); if (estadoTEF != StateTEF.CancelamentoRequisitado) { if (estadoTEF != StateTEF.OperacaoPadrao) { return;//Not sure if this should be return or break... } else { retorno = ContinuaVendaTEFAsync().Result; } } else { statusAtual = StatusTEF.Cancelado; retorno = CancelaOperacaoAtual().Result; this.Dispatcher.Invoke(() => this.Close()); return; } } ... } private void AtualizaUI((string body, string titulo) item) { tbl_Body.Text = item.body.TrimEnd('\0'); //<------ Error thrown here------ lbl_Title.Text = item.titulo.TrimEnd('\0'); } ``` Now I'm getting a different error. Right at the "tbl_Body.Text" line, I got a `System.AccessViolationException` error. Here's the stack trace: > AmbiPDV.exe!PDV_WPF.Telas.SiTEFBox.AtualizaUI(System.ValueTuple<string,string> item = {System.ValueTuple<string,string>}) Line 533 + 0x3 bytes C# mscorlib.dll!System.Progress<System.ValueTuple<string,string>>.InvokeHandlers(object state) + 0x5e bytes WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0xae bytes WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source = {System.Windows.Threading.Dispatcher}, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler = null) + 0x35 bytes WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() + 0xdd bytes WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state) + 0x3f bytes WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(object obj) + 0x42 bytes mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0xc4 bytes mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x17 bytes mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x44 bytes WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext executionContext = {MS.Internal.CulturePreservingExecutionContext}, System.Threading.ContextCallback callback, object state) + 0x9a bytes WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() + 0x50 bytes WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() + 0x176 bytes WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x5c bytes WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd = 5967824, int msg = 49656, System.IntPtr wParam = 0, System.IntPtr lParam = 0, ref bool handled = false) + 0xa1 bytes WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x6c bytes WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x52 bytes WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source = {System.Windows.Threading.Dispatcher}, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler = null) + 0x35 bytes WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) + 0x142 bytes WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd = 5967824, int msg = 49656, System.IntPtr wParam = 0, System.IntPtr lParam = 0) + 0xf4 bytes [Native to Managed Transition] [Managed to Native Transition] WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame = {System.Windows.Threading.DispatcherFrame}) + 0xbb bytes WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x4d bytes PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x60 bytes PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x7a bytes PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x2e bytes PresentationFramework.dll!System.Windows.Application.Run() + 0x1e bytes AmbiPDV.exe!PDV_WPF.App.Main() + 0x5a bytes I read @ https://stackguides.com/questions/48417900/app-crashes-with-attempted-to-read-or-write-protected-memory that this could be caused by passing string literals to functions that expected them to be mutable. However, I believe this is not the case, as I rewrote `AtualizaUI()` as follows:
private void AtualizaUI((string body, string titulo) item) { string a = item.body.TrimEnd('\0'); string b = item.titulo.TrimEnd('\0'); tbl_Body.Text = a; lbl_Title.Text = b; } ```
And once again, I triggered the previous
FatalExecutionError
. Here's the stack strace:AmbiPDV.exe!PDV_WPF.Telas.SiTEFBox.AtualizaUI(System.ValueTuple item = {System.ValueTuple}) Line 536 + 0xc bytes C# mscorlib.dll!System.Progress>.InvokeHandlers(object state) + 0x5e bytes
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0xae bytes
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source = {System.Windows.Threading.Dispatcher}, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler = null) + 0x35 bytes
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() + 0xdd bytes WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(object state) + 0x3f bytes
WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(object obj) + 0x42 bytes
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0xc4 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x17 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x44 bytes
WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext executionContext = {MS.Internal.CulturePreservingExecutionContext}, System.Threading.ContextCallback callback, object state) + 0x9a bytes WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() + 0x50 bytes WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() + 0x176 bytes
WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x5c bytes
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd = 4458568, int msg = 49656, System.IntPtr wParam = 0, System.IntPtr lParam = 0, ref bool handled = false) + 0xa1 bytes
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x6c bytes
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x52 bytes
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source = {System.Windows.Threading.Dispatcher}, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler = null) + 0x35 bytes
WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) + 0x142 bytes
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd = 4458568, int msg = 49656, System.IntPtr wParam = 0, System.IntPtr lParam = 0) + 0xf4 bytes [Native to Managed Transition] [Managed to Native Transition]
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame = {System.Windows.Threading.DispatcherFrame}) + 0xbb bytes
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x4d bytes
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x60 bytes
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x7a bytes
PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x2e bytes
PresentationFramework.dll!System.Windows.Application.Run() + 0x1e bytes AmbiPDV.exe!PDV_WPF.App.Main() + 0x5a bytesOn a side note, I'd like to thank you for pointing me to that article about IProgress. It makes much more sense than lots of await and async voids!
async void
is only meant for event handlers. Change your method syntax toasync Task
at least. Another problem is the attempt to modify the UI from inside a Task. That's whatawait
is for - return execution to the UI context so you don't need to useInvoke
– Panagiotis KanavosShowEF
should beasync Task
itself, so it can useawait ComunicaComTEF()
. That method should beasync Task ComunicaComTEF()
. Instead of wrapping mainly UI code inTask.Run
, only the parts that really need to run in the background should be run this way. This code doesn't seem to contain anything that needsTask.Run
though, it's mainly UI updates – Panagiotis KanavosContinuaVendaTEF()
the method that's blocking? That's the method that should be called insideTask.Run
then. If you want to make polling calls in a tight loop, perhaps a better choice would be to useIProgress<T>
as shown in Enabling progress and cancellation in Async APIs and decouple the UI updates from the polling loop – Panagiotis Kanavos