FatalExecutionEngineError on async method

0

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 and async 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 a while loop, that, for each sync call for the external method calls a this.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://stackoverflow.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://stackoverflow.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 bytes

On 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!

c#
.net
wpf
async-await
c#-8.0
asked on Stack Overflow Feb 1, 2020 by A Sousa • edited Feb 10, 2020 by A Sousa

1 Answer

0

The issue wasn't actually related to asynch programming, initially, but because of unmanaged code and memory handling. Thanks to Alois, who pointed me in the right direction, I managed to find what was wrong.

The external library requires a fixed size byte array (22000 bytes, to be precise), and it must sit at the same address while the library and my software interact. Two big mistakes were made. First, every time I needed to clear the buffer so I could send information to the external library, I was using bufferTEF = new buffer[22000]. So, rather than clearing it, I was instancing a new one, hence changing its address, so the external library had issues finding it again. Second, whenever I sent data to the external library, I used bufferTEF = Encoding.ASCII.GetBytes("information")), which, means I sent back an array with the same length as the information I sent. Since the external library expected a minimum of 20000 bytes, it exploded and didn't know what to do.

answered on Stack Overflow Feb 10, 2020 by A Sousa

User contributions licensed under CC BY-SA 3.0