如何从UI线程开始的任务抛出异常(因此不允许阻塞await)?



目标是启动一个任务,从磁盘上加载一些资源,在UI线程上,然后当它完成时检查它是否抛出了一个异常而不阻塞UI线程,如果是,在UI线程上重新抛出异常,这样程序结束,我得到一个堆栈跟踪。

我已经能够弄清楚我可以在后台线程上启动一个任务而不阻塞,等待任务阻塞主线程。

我绝对不能在任务上调用await,因为它会阻塞UI线程,但似乎我需要在任务完成后调用它来访问异常属性。我也不能使用continueWith,因为它也是异步运行的,所以从那里传播异常将不起作用。

我看到的所有文档都阻塞等待异常。

https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library

https://learn.microsoft.com/en us/dotnet/api/system.threading.tasks.task?view=net - 7.0 # WaitingForOne

对于任务的Exception属性,如果我能使用像RegisterPropertyChangedCallback这样的东西就好了,但这不是任务对象中可用的方法。

这是我想在UI线程中重新抛出异常而不阻塞它的代码

public TitlePage()
{
this.InitializeComponent();
string baseFolder = AppDomain.CurrentDomain.BaseDirectory;
string folder = baseFolder + @"Assets";
Task task = DataSource.Load(folder);
// This is where I want to rethrow the Exception if it was thrown
// unless I can end the program and print the stack trace
// from the Task when it throws an Exception.
// Note that this is inside the constructor of a Page class; 
// it is vital that the UI thread is not blocked.
}

使用"工厂方法":一个静态方法,返回类的新实例。然后将构造函数设置为空且私有,以防止任何人在不使用工厂方法的情况下创建实例。

像这样:

private TitlePage() {}
public static async Task<TitlePage> CreateTitlePage()
{
var titlePage = new TitlePage();
titlePage.InitializeComponent();
string baseFolder = AppDomain.CurrentDomain.BaseDirectory;
string folder = baseFolder + @"Assets";
Task task = DataSource.Load(folder);

await task;
return titlePage;
}

因为这个静态方法在类内部,所以可以调用其他私有方法,包括构造函数。

然后像这样调用它:

var newTitlePage = await TitlePage.CreateTitlePage();

如果在UI线程中调用,则会在UI线程中抛出任何异常。

在UI线程内重新抛出一个由side-task抛出的异常,这只是与UI线程通信的更一般的情况下,由side-task产生的任何类型的结果的特殊情况。

(换句话说,今天你认为你只需要抛出一个异常;明天你可能会发现你也需要一个结果。

为此目的,我喜欢在Scala中找到一个Result类(并且c#中存在各种实现),但这是不必要的;Task.IsCompleted为真后,Task对象也可以通信;UI线程可以用Task.IsFaulted检查是否表示失败,如果是,则用Task.Exception得到异常;否则,使用Task<T>.Result可以得到结果。

所以,现在的问题变成了如何让Task在任务完成后将结果传达给UI线程,而无需UI线程等待任务完成。

为此,您可以向任务传递一个"任务完成操作"。以Action<R>委托的形式,其中R是结果的类型,并以post的方式实现该委托。将结果放入UI线程的消息队列。

在WPF下,通过Dispatcher.BeginInvoke()向UI线程发送内容。

下面是一些代码:

using Sys = System;
using Wpf = System.Windows;
using WpfThreading = System.Windows.Threading;
private void TaskCompletionAction( R result )
{
Assert( not-in-UI-thread );
Sys.Action action = () => ReceiveResult( result );
Wpf.Application.Current.Dispatcher.BeginInvoke(
WpfThreading.DispatcherPriority.Background, 
action );
}
private void ReceiveResult( R result )
{
Assert( in-UI-thread );
...do something with the result...
}

最新更新