gpt4 book ai didi

c# - 来自异步工作器的 UWP 更新 UI

转载 作者:太空宇宙 更新时间:2023-11-03 22:38:56 25 4
gpt4 key购买 nike

我正在尝试实现一个长时间运行的后台进程,该进程会定期报告其进度,以更新 UWP 应用中的 UI。我怎样才能做到这一点?我看过几个有用的主题,但没有一个包含所有部分,而且我无法将它们全部放在一起。

例如,考虑一个用户选择了一个非常大的文件,并且该应用正在读取和/或操作文件中的数据。用户单击一个按钮,该按钮会使用用户选择的文件中的数据填充存储在页面上的列表。

第 1 部分

页面和按钮的点击事件处理程序如下所示:

public sealed partial class MyPage : Page
{
public List<DataRecord> DataRecords { get; set; }

private DateTime LastUpdate;

public MyPage()
{
this.InitializeComponent();

this.DataRecords = new List<DataRecord>();
this.LastUpdate = DateTime.Now;

// Subscribe to the event handler for updates.
MyStorageWrapper.MyEvent += this.UpdateUI;
}

private async void LoadButton_Click(object sender, RoutedEventArgs e)
{
StorageFile pickedFile = // … obtained from FileOpenPicker.

if (pickedFile != null)
{
this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);
}
}

private void UpdateUI(long lineCount)
{
// This time check prevents the UI from updating so frequently
// that it becomes unresponsive as a result.
DateTime now = DateTime.Now;
if ((now - this.LastUpdate).Milliseconds > 3000)
{
// This updates a textblock to display the count, but could also
// update a progress bar or progress ring in here.
this.MessageTextBlock.Text = "Count: " + lineCount;

this.LastUpdate = now;
}
}
}

MyStorageWrapper 类的内部:

public static class MyStorageWrapper
{
public delegate void MyEventHandler(long lineCount);
public static event MyEventHandler MyEvent;

private static void RaiseMyEvent(long lineCount)
{
// Ensure that something is listening to the event.
if (MyStorageWrapper.MyEvent!= null)
{
// Call the listening event handlers.
MyStorageWrapper.MyEvent(lineCount);
}
}

public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
List<DataRecord> recordsList = new List<DataRecord>();

using (Stream stream = await file.OpenStreamForReadAsync())
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();

// Does its parsing here, and constructs a single DataRecord …

recordsList.Add(dataRecord);

// Raises an event.
MyStorageWrapper.RaiseMyEvent(recordsList.Count);
}
}
}

return recordsList;
}
}

我从关注this得到的时间检查代码.

如所写,此代码使应用程序无法响应大文件(我在大约 850 万行的文本文件上进行了测试)。我认为将 asyncawait 添加到 GetDataAsync() 调用会阻止这种情况?这不是在 UI 线程之外的线程上工作吗?通过 Visual Studio 中的 Debug模式,我已验证程序正在按预期进行...它只是占用了 UI 线程,使应用程序无响应(请参阅 this page from Microsoft about the UI thread and asynchronous programming)。

第 2 部分

我已经成功地实现了一个异步的、长时间运行的进程,该进程在一个单独的线程上运行并且仍然定期更新 UI...但是这个解决方案不允许返回值——特别是第 1 部分中的那一行:

this.DataRecords = await MyStorageWrapper.GetDataAsync(pickedFile);

我之前的成功实现如下(为简洁起见,大部分主体都被删掉了)。有没有办法对此进行调整以允许返回值?

Page 类中:

public sealed partial class MyPage : Page
{
public Generator MyGenerator { get; set; }

public MyPage()
{
this.InitializeComponent();

this.MyGenerator = new Generator();
}

private void StartButton_Click(object sender, RoutedEventArgs e)
{
this.MyGenerator.ProgressUpdate += async (s, f) => await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, delegate ()
{
// Updates UI elements on the page from here.
}

this.MyGenerator.Start();
}

private void StopButton_Click(object sender, RoutedEventArgs e)
{
this.MyGenerator.Stop();
}
}

Generator 类中:

public class Generator
{
private CancellationTokenSource cancellationTokenSource;

public event EventHandler<GeneratorStatus> ProgressUpdate;

public Generator()
{
this.cancellationTokenSource = new CancellationTokenSource();
}

public void Start()
{
Task task = Task.Run(() =>
{
while(true)
{
// Throw an Operation Cancelled exception if the task is cancelled.
this.cancellationTokenSource.Token.ThrowIfCancellationRequested();

// Does stuff here.

// Finally raise the event (assume that 'args' is the correct args and datatypes).
this.ProgressUpdate.Raise(this, new GeneratorStatus(args));
}
}, this.cancellationTokenSource.Token);
}

public void Stop()
{
this.cancellationTokenSource.Cancel();
}
}

最后,ProgressUpdate 事件有两个支持类:

public class GeneratorStatus : EventArgs
{
// This class can contain a handful of properties; only one shown.
public int number { get; private set; }

public GeneratorStatus(int n)
{
this.number = n;
}
}

static class EventExtensions
{
public static void Raise(this EventHandler<GeneratorStatus> theEvent, object sender, GeneratorStatus args)
{
theEvent?.Invoke(sender, args);
}
}

最佳答案

了解 async/await 并不直接表示等待的代码将在不同的线程上运行是关键。当您执行 await GetDataAsync(pickedFile); 时,执行进入仍在 UI 线程上的 GetDataAsync 方法并继续执行,直到 await file.OpenStreamForReadAsync() 已达到 - 这是唯一操作,它实际上将在不同的线程上异步运行(因为 file.OpenStreamForReadAsync 实际上是以这种方式实现的)。

但是,一旦 OpenStreamForReadAsync 完成(这将非常快),await 确保执行返回到它开始的同一个线程 - 这意味着 界面线程。因此,代码中实际开销较大的部分(在 while 中读取文件)在 UI 线程上运行。

您可以通过使用 reader.ReadLineAsync 略微改进这一点,但您仍然会在每次 await 后返回到 UI 线程。

ConfigureAwait(假)

要解决此问题的第一个技巧是 ConfigureAwait(false)

在异步调用上调用它会告诉运行时执行不必返回到最初调用异步方法的线程 - 因此这可以避免将执行返回到 UI 线程。将它放在你的案例中的好地方是 OpenStreamForReadAsyncReadLineAsync 调用:

public static async Task<List<DataRecord>> GetDataAsync(StorageFile file)
{
List<DataRecord> recordsList = new List<DataRecord>();

using (Stream stream = await file.OpenStreamForReadAsync().ConfigureAwait(false))
{
using (StreamReader reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
string line = await reader.ReadLineAsync().ConfigureAwait(false);

// Does its parsing here, and constructs a single DataRecord …

recordsList.Add(dataRecord);

// Raises an event.
MyStorageWrapper.RaiseMyEvent(recordsList.Count);
}
}
}

return recordsList;
}

调度员

现在您释放了 UI 线程,但引入了进度报告的另一个问题。因为现在 MyStorageWrapper.RaiseMyEvent(recordsList.Count) 在不同的线程上运行,所以您不能在 UpdateUI 方法中直接更新 UI,因为访问来自非 UI 线程的 UI 元素抛出同步异常。相反,您必须使用 UI 线程 Dispatcher 来确保代码在正确的线程上运行。

在构造函数中获取对 UI 线程 Dispatcher 的引用:

private CoreDispatcher _dispatcher;

public MyPage()
{
this.InitializeComponent();
_dispatcher = Window.Current.Dispatcher;

...
}

提前这样做的原因是 Window.Current 再次只能从 UI 线程访问,但页面构造函数肯定在那里运行,因此它是理想的使用位置。

现在重写UpdateUI如下

private async void UpdateUI(long lineCount)
{
await _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// This time check prevents the UI from updating so frequently
// that it becomes unresponsive as a result.
DateTime now = DateTime.Now;
if ((now - this.LastUpdate).Milliseconds > 3000)
{
// This updates a textblock to display the count, but could also
// update a progress bar or progress ring in here.
this.MessageTextBlock.Text = "Count: " + lineCount;

this.LastUpdate = now;
}
});
}

关于c# - 来自异步工作器的 UWP 更新 UI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53463932/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com