c#从多个并行运行的任务中更新UI
本文关键字:任务 更新 UI 运行 并行 | 更新日期: 2023-09-27 18:07:44
我有一个关于UI和从任务更新它的问题。在尝试将我的应用程序从winforms移植到UWP时,在此过程中,我想优化应用程序中CPU繁重的部分。
以前我使用后台工作器来运行计算,但是使用Task API,我可以大大提高速度。当尝试更新UI时出现问题。
我正在扫描DNA链,寻找我的一些"特征"。
-
当扫描开始时,我想用当前的'task'更新UI上的标签。
-
当扫描完成时,我想发送功能的"大小",这样我就可以更新UI(进度条和标签)与扫描的数据量。
-
如果功能被发现,我想把它发送到UI在listview中显示
我当前的代码在一定程度上工作。它扫描DNA,找到功能并更新UI。然而,UI经常冻结,有时在整个过程中只更新几次。
我已经在网上搜索了几天,试图解决我的问题,但我不知道最好的方法,或者我是否应该简单地放弃任务,回到一个单一的后台工作者。
所以我的问题是解决这个问题的正确方法是什么。
我如何设置我的任务,并报告回UI线程在一个可靠的方式从多个任务在同一时间?
我已经写了一个代码示例,类似于我目前的设置:
public class Analyzer
{
public event EventHandler<string> ReportCurrent;
public event EventHandler<double> ReportProgress;
public event EventHandler<object> ReportObject;
private List<int> QueryList; //List of things that need analysis
public Analyzer()
{
}
public void Start()
{
Scan();
}
private async void Scan()
{
List<Task> tasks = new List<Task>();
foreach (int query in QueryList)
{
tasks.Add(Task.Run(() => ScanTask(query)));
}
await Task.WhenAll(tasks);
}
private void ScanTask(int query)
{
ReportCurrent?.Invoke(null, "name of item being scanned");
bool matchfound = false;
//Do work proportional with the square of 'query'. Values range from
//single digit to a few thousand
//If run on a single thread completion time is around 10 second on
//an i5 processor
if (matchfound)
{
ReportObject?.Invoke(null, query);
}
ReportProgress?.Invoke(null, query);
}
}
public sealed partial class dna_analyze_page : Page
{
Analyzer analyzer;
private void button_click(object sender, RoutedEventArgs e)
{
analyzer = new Analyzer();
analyzer.ReportProgress += new EventHandler<double>(OnUpdateProgress);
analyzer.ReportCurrent += new EventHandler<string>(OnUpdateCurrent);
analyzer.ReportObject += new EventHandler<object>(OnUpdateObject);
analyzer.Start();
}
private async void OnUpdateProgress(object sender, double d)
{
//update value of UI element progressbar and a textblock ('label')
//Commenting out all the content the eventhandlers solves the UI
//freezing problem
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { /*actual code here*/});
}
private async void OnUpdateCurrent(object sender, string s)
{
//update value of UI element textblock.text = s
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { });
}
private async void OnUpdateObject(object sender, object o)
{
//Add object to a list list that is bound to a listview
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { });
}
}
我希望我的问题是清楚的。谢谢你。当前的解决方案和唯一的解决方案,我已经能够找到到目前为止我没有同时启动281个任务,而是启动4个并等待它们完成:
List<Task> tasks = new List<Task>();
for (int l = 0; l < QueryList.Count; l++)
{
Query query= QueryList[l];
tasks.Add(Task.Run(() => { ScanTask(query); }, taskToken));
//somenumber = number of tasks to run at the same time.
//I'm currently using a number proportional to the number of logical processors
if (l % somenumber == 0 || l == QueryList.Count + 1)
{
try
{
await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
datamodel.Current = "Aborted";
endType = 1; //aborted
break;
}
catch
{
datamodel.Current = "Error";
endType = 2; //error
break;
}
}
}
你可以调用一个函数回UI线程:
MethodInvoker mI = () => {
//this is from my code - it updates 3 textboxes and one progress bar.
//It's intended to show you how to insert different commands to be invoked -
//basically just like a method. Change these to do what you want separated by semi-colon
lbl_Bytes_Read.Text = io.kBytes_Read.ToString("N0");
lbl_Bytes_Total.Text = io.total_KB.ToString("N0");
lbl_Uncompressed_Bytes.Text = io.mem_Used.ToString("N0");
pgb_Load_Progress.Value = (int)pct;
};
BeginInvoke(mI);
要将此应用于您的需求,请让您的任务更新类或队列,然后使用单个BeginInvoke将其清空到UI中。
class UI_Update(){
public string TextBox1_Text {get;set;}
public int progressBar_Value = {get;set;}
//...
System.ComponentModel.BackgroundWorker updater = new System.ComponentModel.BackgroundWorker();
public void initializeBackgroundWorker(){
updater.DoWork += UI_Updater;
updater.RunWorkerAsync();
}
public void UI_Updater(object sender, DoWorkEventArgs e){
bool isRunning = true;
while(isRunning){
MethodInvoker mI = () => {
TextBox1.Text = TextBox1_Text;
myProgessBar.Value = progressBar.Value;
};
BeginInvoke(mI);
System.Threading.Thread.Sleep(1000);
}
}
}
PS -这里可能有一些拼写错误。我不得不像昨天一样离开,但我想把我的观点说清楚。我稍后再编辑。
EDIT for UWP, try
CoreDispatcher dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
});
根据我的经验,Dispatcher。当RunAsync可以经常引发时,它不是一个好的解决方案,因为您无法知道它何时运行。
在dispatcher队列中添加的工作可能会超过UI线程的执行能力。
另一个解决方案是创建线程任务之间共享的线程安全模型,并使用DispatcherTimer更新UI。
这里是一个示例草图:
public sealed partial class dna_analyze_page : Page
{
Analyzer analyzer;
DispatcherTimer dispatcherTimer = null; //My dispatcher timer to update UI
TimeSpan updatUITime = TimeSpan.FromMilliseconds(60); //I update UI every 60 milliseconds
DataModel myDataModel = new DataModel(); //Your custom class to handle data (The class must be thread safe)
public dna_analyze_page(){
this.InitializeComponent();
dispatcherTimer = new DispatcherTimer(); //Initilialize the dispatcher
dispatcherTimer.Interval = updatUITime;
dispatcherTimer.Tick += DispatcherTimer_Tick; //Update UI
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this.dispatcherTimer.Start(); //Start dispatcher
}
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
base.OnNavigatingFrom(e);
this.dispatcherTimer.Stop(); //Stop dispatcher
}
private void DispatcherTimer_Tick(object sender, object e)
{
//Update the UI
myDataModel.getProgress()//Get progess data and update the progressbar
//etc...
}
private void button_click(object sender, RoutedEventArgs e)
{
analyzer = new Analyzer();
analyzer.ReportProgress += new EventHandler<double>(OnUpdateProgress);
analyzer.ReportCurrent += new EventHandler<string>(OnUpdateCurrent);
analyzer.ReportObject += new EventHandler<object>(OnUpdateObject);
analyzer.Start();
}
private async void OnUpdateProgress(object sender, double d)
{
//update value of UI element progressbar and a textblock ('label')
//Commenting out all the content the eventhandlers solves the UI
//freezing problem
myDataModel.updateProgress(d); //Update the progress data
}
private async void OnUpdateCurrent(object sender, string s)
{
//update value of UI element textblock.text = s
myDataModel.updateText(s); //Update the text data
}
private async void OnUpdateObject(object sender, object o)
{
//Add object to a list list that is bound to a listview
myDataModel.updateList(o); //Update the list data
}
}
如果您想对集合中的每个元素运行相同的操作,我会选择Parallel.ForEach。
技巧是在ForEach代码中使用IProgress<T>
来向主线程报告更新。IProgress<T>
构造函数接受一个匿名函数,该函数将在主线程中运行,因此可以更新UI。
引自https://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html:
public async void StartProcessingButton_Click(object sender, EventArgs e)
{
// The Progress<T> constructor captures our UI context,
// so the lambda will be run on the UI thread.
var progress = new Progress<int>(percent =>
{
textBox1.Text = percent + "%";
});
// DoProcessing is run on the thread pool.
await Task.Run(() => DoProcessing(progress));
textBox1.Text = "Done!";
}
public void DoProcessing(IProgress<int> progress)
{
for (int i = 0; i != 100; ++i)
{
Thread.Sleep(100); // CPU-bound work
if (progress != null)
progress.Report(i);
}
}
我创建了一个IEnumerable<T>
扩展来运行一个并行的事件回调,可以直接修改UI。你可以在这里查看:
希望有帮助!