2016-09-18 30 views
0

我有一个关于UI的问题并从任务更新它。 试图将我的应用程序从winforms移植到UWP,并且在此过程中,我想优化应用程序的CPU大部分。C#从并行运行的多个任务更新UI

以前我用背景工作来运行计算,但是使用Task API,我可以提高很多速度。尝试更新UI时出现问题。

我正在扫描DNA链中的一些“特征”,我有。

  • 当扫描开始时,我想用当前的'任务'在UI上更新标签。

  • 扫描完成后,我想发送该功能的“大小”,以便可以使用扫描的数据量更新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

它是'Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync'吗? –

+0

是: 'await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,()=> {});' 我也尝试过使用'调度员“,但结果相似。 –

回答

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); 

若要将此您的需求,让您的任务更新类或队列,然后使用它清空到UI一个BeginInvoke。

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的;

+0

因此,在dna_analyzer_page(UI)上构建一个'队列'类对象,并将其传递给将数据添加到队列而不是抛出事件的分析器。然后在UI上使用一个计时器来“BeginInvoke(mI)”数据,就像你的例子一样? 'MethodInvoker mI =()=> {label.text = queue.someproperty etc。};' –

+0

是的 - 如果您的所有更新都是基于文本的,那么您可以使用队列。如果它们包含不同的更新(如textbox.text和progressbar.value),则创建一个具有与您的控件匹配的属性的类。将值从您的各个线程中提供给属性。也许在该类中,使用System.Component.BackgroundWorker每秒更新UI屏幕... –

+0

看起来UWP没有MethodInvoker和BeginInvoke方法。 –

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 
    } 
} 
+0

这可能是解决方案。我现在实际上使用一个计时器(100ms)来一次更新所有UI元素。 你能帮我解决一下如何设计线程安全类以及'getProgress'方法的外观吗? –

+0

您可以创建私有财产进度,并获得应该简单地返回财产。 对于线程安全问题是在集合中,因为进度是一个原始类型,您可以使用Interlocked.Exchange(ref progressProperty,value); 相反,如果你需要设置一个对象,你应该锁定对象。 –

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>扩展到用于与事件回调可以直接修改UI运行平行。你可以在它一看这里:

https://github.com/jotaelesalinas/csharp-forallp

希望它能帮助!