我有关于UI的问题并从任务中更新它。 尝试将我的应用程序从winforms移植到UWP时,在此过程中我想优化应用程序的CPU重量部分。
以前我使用后台工作程序来运行计算,但是使用Task API,我可以大大提高速度。尝试更新UI时出现问题。
我正在扫描DNA链以获得我所拥有的许多“特征”。
启动扫描时,我想使用当前的“任务”更新UI上的标签。
扫描完成后,我想发送该功能的“大小”,以便我可以使用扫描的数据量更新UI(进度条和标签)。
如果找到该功能,我想将其发送到用户界面,以便在列表视图中显示。
我目前的代码在某种程度上有效。它扫描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;
}
}
}
答案 0 :(得分:0)
您可以将函数调用回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 - 这里可能会有一些误拼写。我必须像昨天一样离开,但我希望得到我的观点。我稍后会修改。
对于UWP,编辑,请尝试
CoreDispatcher dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
});
取代BeginInvoke;
答案 1 :(得分:0)
根据我的经验,Dispatcher.RunAsync在经常被引发时不是一个好的解决方案,因为你无法知道它何时会运行。
您可能会在调度程序队列中添加比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
}
}
答案 2 :(得分:0)
如果你想为集合的每个元素运行相同的动作,我会选择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>
扩展来运行并行for for事件回调,可以直接修改UI。你可以在这看看它:
https://github.com/jotaelesalinas/csharp-forallp
希望它有所帮助!