【发布时间】:2020-05-17 22:13:12
【问题描述】:
好的。因此,根据我被指出的文章,我继续重写了大部分代码。
看起来像这样:
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 是一个开关,根据comando 执行某些操作,例如显示消息
private void ExibeMensagemOperador(byte[] buffer)
{
tituloJanela = "OPERAÇÃO NO TEF";
mensagemJanela = Encoding.ASCII.GetString(buffer).Split('\0')[0];
}
或者要求用户按任意键
public void PerguntaSimOuNao(byte[] pergunta)
{
estadoTEF = StateTEF.AguardaSimNao;
mensagemJanela = "(S)im / (N)ão";
tituloJanela = Encoding.ASCII.GetString(pergunta).Split('\0')[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);
}
}
现在,获取新信息。当我使用任务运行它时,没有异步/等待,只返回一个任务,其结果同步触发 FatalExecutionError。如果ComunicaComTef为int,去掉Task.Run(只是同步运行代码),不会触发错误,循环运行完美。
问题的先前版本(如果需要):
过去几个月我一直在学习异步编程,而且我已经 发现一个我不知道如何调试/处理的错误:
这是设置。我有一个窗口
ShowTEF,它调用了两个方法,IniciaFuncaoSitef和async ComunicaComTEF。两人打电话 外部 dll 方法,返回整数值和一个 byte[] by ref.
IniciaFuncaoSitef简单地启动一个操作,通过提供一些 外部dll的参数。ComunicaComTEF有一个while循环, 也就是说,对于外部方法的每个同步调用调用一个this.Dispatcher.Invoke()刷新用户界面。这是简化的 代码: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; } ```再一次,我触发了之前的
FatalExecutionError。这里是 堆栈跟踪:AmbiPDV.exe!PDV_WPF.Telas.SiTEFBox.AtualizaUI(System.ValueTuple item = {System.ValueTuple}) 第 536 行 + 0xc 字节 C# mscorlib.dll!System.Progress>.InvokeHandlers(对象 状态)+ 0x5e 字节
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate 回调、对象 args、int numArgs) + 0xae 字节
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(对象 源 = {System.Windows.Threading.Dispatcher},System.Delegate 回调,对象参数,int numArgs,System.Delegate catchHandler = null) + 0x35 字节
WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeImpl() + 0xdd 字节 WindowsBase.dll!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(对象 状态)+ 0x3f 字节
WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(对象 obj) + 0x42 字节
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 state, bool preserveSyncCtx) + 0xc4 字节
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 状态,bool preserveSyncCtx) + 0x17 字节
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext、System.Threading.ContextCallback 回调、对象 状态)+ 0x44 字节
WindowsBase.dll!MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext executionContext = {MS.Internal.CulturePreservingExecutionContext}, System.Threading.ContextCallback 回调,对象状态)+ 0x9a 字节 WindowsBase.dll!System.Windows.Threading.DispatcherOperation.Invoke() + 0x50 字节 WindowsBase.dll!System.Windows.Threading.Dispatcher.ProcessQueue() + 0x176 字节
WindowsBase.dll!System.Windows.Threading.Dispatcher.WndProcHook(System.IntPtr hwnd,int msg,System.IntPtr wParam,System.IntPtr lParam,ref bool 已处理)+ 0x5c 字节
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd = 4458568,int msg = 49656,System.IntPtr wParam = 0,System.IntPtr lParam = 0, ref bool 处理 = false) + 0xa1 字节
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(对象 o) + 0x6c 字节
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate 回调,对象 args,int numArgs) + 0x52 字节
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(对象 源 = {System.Windows.Threading.Dispatcher},System.Delegate 回调,对象参数,int numArgs,System.Delegate catchHandler = null) + 0x35 字节
WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority 优先级、System.TimeSpan 超时、System.Delegate 方法、对象 args, int numArgs) + 0x142 字节
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 过渡] [托管到本机过渡]
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame 帧 = {System.Windows.Threading.DispatcherFrame}) + 0xbb 字节
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame 帧)+ 0x4d 字节
PresentationFramework.dll!System.Windows.Application.RunDispatcher(对象 忽略)+ 0x60 字节
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window 窗口)+ 0x7a 字节
PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window 窗口)+ 0x2e 字节
PresentationFramework.dll!System.Windows.Application.Run() + 0x1e 字节 AmbiPDV.exe!PDV_WPF.App.Main() + 0x5a 字节顺便说一句,我要感谢您将我指向那篇文章 关于 IProgress。它比大量的 await 和 async 更有意义 空虚!
【问题讨论】:
-
请分享调用堆栈
-
async void仅用于事件处理程序。至少将您的方法语法更改为async Task。另一个问题是尝试从任务内部修改 UI。这就是await的用途 - 将执行返回到 UI 上下文,因此您不需要需要使用Invoke -
这意味着
ShowEF本身应该是async Task,所以它可以使用await ComunicaComTEF()。该方法应该是async Task ComunicaComTEF()。而不是主要将 UI 代码包装在Task.Run中,只有真正需要在后台运行的部分应该以这种方式运行。这段代码似乎没有包含任何需要Task.Run的东西,它主要是 UI 更新 -
ContinuaVendaTEF()是阻塞的方法吗?这就是应该在Task.Run内部调用的方法。如果你想在一个紧密的循环中进行轮询调用,也许更好的选择是使用IProgress<T>,如Enabling progress and cancellation in Async APIs 所示,并将 UI 更新与轮询循环解耦 -
ExecutionError 意味着经常出现非托管内存损坏。这通常是由错误的 PInvoke 签名引起的。尝试在 VS 中启用 MDA 以捕获那里的常见错误。另一个来源是您的缓冲区。您将它们放在通过引用传递的托管代码中,这些代码可以在您调用它们时进行 GC、移动。这可能会导致有趣的竞争条件,GC 认为没有人在使用该数组并移动数据。这可能导致托管堆损坏,您可以在托管内存中写入任意位置。您是否正确固定数据?
标签: c# .net wpf async-await c#-8.0