如何强制一个任务在UI线程上运行
本文关键字:UI 线程 运行 任务 一个 何强制 | 更新日期: 2023-09-27 17:50:57
原文如下。让我试着更详细地解释一下我为什么要这样做。
我有一个页面侦听共享魅力请求:
void Page_Loaded(object sender, RoutedEventArgs e)
{
m_transferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.GetForCurrentView();
m_transferManager.DataRequested += TransferManager_DataRequested;
}
当事件触发(TransferManager_DataRequested
)时,它不会在UI线程上触发:
void TransferManager_DataRequested(DataTransferManager sender, DataRequestedEventArgs args)
{
var data = args.Request.Data;
// More related stuff omitted - not important.
data.SetDataProvider(StandardDataFormats.Bitmap, GetImage_DelayRenderer);
}
现在,当GetImage_DelayRender
被调用时,它也不会在UI线程上被调用。然而,在其中,我需要做一些UI相关的事情。具体来说,我需要调用一个只在UI上工作的方法(这是我在其他地方使用的方法,我想重用它的逻辑)。该方法被称为GetImageAsync
,它需要在UI上运行,因为它与WriteableBitmap进行了一系列交互。它还做了一堆异步操作(如写入流等),这就是为什么它是异步的。我尽可能短地阻塞GetImageAsync()
上的UI。
GetImage_DelayRender
是这样的:
private async void GetImage_DelayRenderer(DataProviderRequest request)
{
var deferral = request.GetDeferral();
await Dispatcher.RunTask(async () => // RunTask() is an extension method - described in the original question below.
{
try
{
var bitmapStream = await GetImageAsync();
request.SetData(RandomAccessStreamReference.CreateFromStream(bitmapStream));
}
catch
{
}
});
deferral.Complete();
}
我想知道的是,什么是最正确的方式来实现调用Dispatcher.RunTask()
上面(这是我的hack扩展方法)。
-----开始原始消息-------
假设我有以下任务:
private async Task SomeTask()
{
await Task.Delay(1000);
// Do some UI and other stuff that may also be async
}
Edit(澄清):我不想阻塞UI。我想要执行的任务(即使在例子中,如果你读它)不会阻塞UI。我只想让任务在UI的上下文中运行,因为它的同步部分。
我想在UI线程上运行这个代码作为异步操作。Dispatcher.RunXXX()方法接受一个操作,这意味着它们将运行该操作并在完成时通知您。这还不够好。我需要整个任务在UI线程上运行(因为如果我从UI线程运行它就会执行),然后,当完成时,通知我回来。
我能想到的唯一方法是使用Dispatcher.RunXXX()方法来执行一个匿名委托,该委托在我的方法中为任务设置一个局部变量,然后等待…
public async static Task RunTask(this CoreDispatcher dispatcher, Func<Task> taskGiver)
{
Task task = null;
await dispatcher.RunAsync(() => task = taskGiver());
await task;
}
这看起来很丑。有更好的方法吗?
Edit2:伙计们-阅读这段代码-如果我使用RunTask() hack执行上面的第一个代码块,它不会阻塞Task.Delay()上的UI…
我想在UI线程的代码上运行这个异步操作。
然后直接运行:
async void MyEventHandler(object sender, ...)
{
await SomeTask();
}
更新:
我不确定这是一个"合法"的操作,但你可以通过在UI处于活动状态时捕获CoreDispatcher
并稍后调用RunAsync
来调度该方法在UI线程上运行:
private async void GetImage_DelayRenderer(DataProviderRequest request)
{
var deferral = request.GetDeferral();
Task task = null;
await coreDispatcher.RunAsync(() => { task = SomeTask(); });
await task;
deferral.Complete();
}
我没有时间做一个完整的解决方案,所以希望你仍然会发现这是有用的…
首先,正如其他人指出的那样,你不能在UI线程上运行某些东西而不让它阻塞UI线程。讨论结束。你说你需要的是在非UI线程上运行的东西,并定期通知UI线程有需要处理的更新。
要完成此操作,您需要这样做…
public class LongTask
{
public event EventHandler MyEvent;
public void Execute()
{
var task = Task.Factory.StartNew(() =>
{
while (true)
{
// condition met to notify UI
if (MyEvent != null)
MyEvent(this, null);
}
});
}
}
在你的UI中,做一些像…
private void button_Click(object sender, RoutedEventArgs e)
{
var test = new LongTask();
test.MyEvent += test_MyEvent;
test.Execute();
}
void test_MyEvent(object sender, EventArgs e)
{
Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
test.Text += " bang ";
});
显然,你可以使用MVVM之类的东西以更简洁的方式实现它,但这是基本的思想。}
我是这样做的:
public static Task<string> GetResultAsync()
{
return Task<string>.Factory.StartNew(() => GetResultSync());
}
在UI: private async void test()
{
string result = await GetResultAsync();
// update UI no problem
textbox.Text = result;
}