EDITSolved,见下面/编辑
这是个新手问题。
我只是在钻研c#和异步,为什么我想要:
现在,我可以单击botton并启动任务链,但是在我希望(用于测试)的完成事件中,每次任务完成时都会显示一个消息框。这可能会导致坠机(?)我不知道为什么,因为我以为我会在ui线程中.
下面是代码的一些部分:
AppViewModel:
void handlePhaseCompletedEvent(object sender, SyncPhaseCompletedEventArgs e)
{
Shell.Current.DisplayAlert("TEST", "PHASE " + e.phase.ToString(), "OK"); // <<<< doesn't show up, maybe because its crashing a short time after?
syncToolService.StartSyncPhaseAsync(e.phase + 1, this); // <<<< seems to crash here?
}
[RelayCommand]
async Task StartSyncAsync()
{
syncToolService.NotifySyncPhaseCompleted += handlePhaseCompletedEvent;
syncToolService.StartSyncPhaseAsync(0, this);
}
syncToolService:
public event EventHandler<SyncPhaseCompletedEventArgs> NotifySyncPhaseCompleted;
public async Task StartSyncPhaseAsync(int phase, AppViewModel viewModel)
{
// search for Remote-peer
if (phase == 0)
{
Task t = new Task(() => Task.Delay(100)); // dummy, not implemented yet
t.ConfigureAwait(false);
t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
t.Start();
return;
}
// Remote Sync start preparations
if (phase == 1)
{
Task t = new Task(() => Task.Delay(100)); // dummy, not implemented yet
t.ConfigureAwait(false);
t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
t.Start();
return;
}
//////// LOCAL PREPARATIONS
// read local files
if (phase == 2)
{
Task t = new Task(() => BPMSyncToolService.loadLocalData(viewModel.DataFiles));
t.ConfigureAwait(false);
t.ContinueWith(t => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
t.Start();
return;
}
}
基本上,我认为StartSyncPhaseAsync会运行一个任务(它似乎也会这样做),并且在调试过程中逐行运行时,它似乎也会触发事件(似乎不会引发exeption),在使用这个堆栈的syncToolService.StartSyncPhaseAsync(e.phase + 1, this);
之后,它会崩溃:
> [Exception] WinRT.Runtime.dll!WinRT.ExceptionHelpers.ThrowExceptionForHR.__Throw|20_0(int hr)
[Exception] Microsoft.WinUI.dll!Microsoft.UI.Xaml.Controls.ContentDialog._IContentDialogFactory.CreateInstance(object baseInterface, out System.IntPtr innerInterface)
[Exception] Microsoft.WinUI.dll!Microsoft.UI.Xaml.Controls.ContentDialog.ContentDialog()
[Exception] Microsoft.Maui.Controls.dll!Microsoft.Maui.Controls.Platform.AlertManager.AlertRequestHelper.OnAlertRequested(Microsoft.Maui.Controls.Page sender, Microsoft.Maui.Controls.Internals.AlertArguments arguments)
System.Private.CoreLib.dll!System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Private.CoreLib.dll!System.Threading.Tasks.Task.ThrowAsync.AnonymousMethod__128_1(object state)
System.Private.CoreLib.dll!System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute()
System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()
System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()
我也可能在我的设计中有一个普遍的问题,任何帮助都将是伟大的!
更新它现在按预期运行。新手-想法:
因此,这是一个基本的实际工作代码,它似乎没有锁定UI,也没有崩溃(崩溃是因为Microsoft.VisualBasic.FileIO.TextFieldParser试图读取一行,并找到了一个以引号开头的字段,并认为它将是一个封闭的引号,而它不是)
AppViewModel:
private void HandleSyncProgressChanged(object sender, SyncStatus e)
{
NumFilesProcessed = e.filesProcessed;
NumFilesNotFound = e.filesNotFound;
AktueleAufgabe = e.workingPhase;
}
[RelayCommand]
async Task StartSyncAsync()
{
Progress<SyncStatus> progress=new Progress<SyncStatus>();
progress.ProgressChanged += HandleSyncProgressChanged;
await BPMSyncToolService.StartSyncPhaseAsync(this, progress);
}
syncToolService:
public static async Task StartSyncPhaseAsync(AppViewModel viewModel, IProgress<SyncStatus> progress)
{
SyncStatus report = new SyncStatus();
report.workingPhase = "Suche Synchronisationspartner";
progress.Report(report);
// search for Remote-peer
await Task.Delay(100); // dummy, not implemented yet
report.workingPhase = "Starte Vorbereitungen beim Synchronisationspartner";
progress.Report(report);
// Remote Sync start preparations
await Task.Delay(100); // dummy, not implemented yet
//////// LOCAL PREPARATIONS
report.workingPhase = "lese lokale Dateien";
progress.Report(report);
// read local files
await BPMSyncToolService.LoadLocalDataAsync(viewModel.DataFiles, progress, report);
// [...]
}
实际上我看不到的是处理过的文件的计数,也许它太快了,不知道,会在需要更多时间的进一步任务中看到
不管怎样,谢谢,这两个答案都有帮助,我会把这个问题标记为解决方案,那是更接近核心问题的(我想)
发布于 2022-08-18 21:04:01
考虑到async
/await
,几乎没有必要使用task continuations
或ConfigureAwait
。
Task.Run
中。Dispatcher.Dispatch
。示例:
// IMPORTANT: `await`.
// Otherwise, current method would continue before Task.Run completes.
await Task.Run(async () =>
{
// Now on background thread.
...
// Report progress to UI.
Dispatcher.Dispatch(() =>
{
// Code here is queued to run on MainThread.
// Assuming you don't need to wait for the result,
// don't need await/async here.
}
// Still on background thread.
...
};
// This is effectively the "continuation": Code here runs after Task.Run completes.
...
更新
作为对评论的回应,您可以使用异步/等待来启动一系列任务,而无需等待结果:
如果顶级代码执行UI调用:
// This queues an independent execution to MainThread.
// We don't "await" the Dispatch, because we want it to run independently.
Dispatcher.Dispatch(async () => await TopMethod());
如果顶层代码不执行UI调用:
// This queues an independent execution to the Thread Pool.
// We don't "await" the Run, because we want it to run independently.
Task.Run(async () => await TopMethod());
在任何一种情况下,TopMethod都不使用连续,而是使用await
来排序任务:
async void TopMethod()
{
await ..Task1..;
await ..Task2..;
await ..Task3..;
}
这相当于Task1.ContinueWith(Task2.ContinueWith(Task3));
(在我的头上,我可能没有完全正确的语法)。
如果您处于后台线程(做了Task.Run
),那么要执行UI调用,只需在Dispatcher.Dispatch( ... )
中包装即可。如第一个代码片段所示。
发布于 2022-08-18 18:13:45
您可以在构造函数中捕获SynchronizationContext,也可以通过为捕获定义显式API来捕获syncToolService:
public void CaptureSynchronizationContext(SynchronizationContext context)
{
var current = SynchronizationContext.Current;
if (context is null)
{
this.capturedScheduler = TaskScheduler.Current;
return;
}
SynchronizationContext.SetSynchronizationContext(context);
this.capturedScheduler = TaskScheduler.FromCurrentSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(current);
}
为在指定的上下文中调用逻辑添加一些包装器:
private void RunTaskWithContinuation(Task task, Action<Task> continuation)
{
task.ConfigureAwait(false);
task.ContinueWith(t => continuation(t), capturedScheduler);
task.Start();
}
因此,在您的UI中的某个位置:
// afaik you should call it once per every Window
syncToolService.CaptureSynchronizationContext(SynchronizationContext.Current);
上面的代码如下所示:
// read local files
if (phase == 2)
{
Task t = new Task(() => BPMSyncToolService.loadLocalData(viewModel.DataFiles));
RunTaskWithContinuation(t, () => NotifySyncPhaseCompleted?.Invoke(this, new SyncPhaseCompletedEventArgs { phase = phase }));
}
没有经过测试,但我会先尝试一下这个想法。
顺便说一句,如果SynchronizationContext为null,则猜测您的问题将持续存在。
这里有重构的空间,只是想展示一下这个想法。
更新
在多线程环境中,有适合于报表的ReportProgress型工具。也许这就是你要找的。
但是它的工作方式和我上面做的一样--通过上下文捕获。
https://stackoverflow.com/questions/73403608
复制相似问题