异步方法上的 FatalExecutionEngineError

FatalExecutionEngineError on async method

好的。因此,根据我被指出的文章,我继续重写了大部分代码。

看起来像这样:

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('[=10=]')[0], 0);
            var retorno = ContinuaFuncaoSiTefInterativo(ref Comando, ref TipoCampo, ref TamMinimo, ref TamMaximo, bufferTEF, bufferTEF.Length, 0);
            ProcessaComando(Comando, bufferTEF);
            LimpaBuffer();
            return retorno;
        }

ProcessaComando 是一个开关,它根据 comando 做一些事情,比如显示消息

private void ExibeMensagemOperador(byte[] buffer)
{
    tituloJanela = "OPERAÇÃO NO TEF";
    mensagemJanela = Encoding.ASCII.GetString(buffer).Split('[=11=]')[0];
}

或者要求用户按任意键

public void PerguntaSimOuNao(byte[] pergunta)
{
    estadoTEF = StateTEF.AguardaSimNao;
    mensagemJanela = "(S)im / (N)ão";
    tituloJanela = Encoding.ASCII.GetString(pergunta).Split('[=12=]')[0];
}

然后由 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);
            }
        }

现在,获取新信息。当我 运行 它使用任务时,没有 async/await,只返回一个任务,它的结果同步触发 FatalExecutionError。如果 ComunicaComTef 为 int,并删除 Task.Run(只是 运行 同步代码),则不会触发错误,并且循环 运行 是完美的。


问题的上一个版本,如果需要的话:

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
(
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('[=14=]'); //<------ Error thrown here------
    lbl_Title.Text = item.titulo.TrimEnd('[=14=]'); } ```

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 @

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('[=15=]');
        string b = item.titulo.TrimEnd('[=15=]');
        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

顺便说一下,我想感谢你指点我那篇文章 关于 IProgress。它比大量等待和异步更有意义 空洞!

最初,该问题实际上与异步编程无关,而是与非托管代码和内存处理有关。感谢 Alois,他为我指明了正确的方向,我设法找到了问题所在。

外部库需要一个固定大小的字节数组(准确地说是 22000 字节),并且在库和我的软件交互时它必须位于同一地址。犯了两个大错误。首先,每次我需要清除缓冲区以便将信息发送到外部库时,我都使用 bufferTEF = new buffer[22000]。因此,我没有清除它,而是实例化了一个新的,因此更改了它的地址,因此外部库再次找到它时遇到问题。其次,每当我向外部库发送数据时,我都使用bufferTEF = Encoding.ASCII.GetBytes("information")),这意味着我发回了一个与我发送的信息长度相同的数组。由于外部库要求最少20000字节,炸了不知道怎么办。