【问题标题】:C# Updating UI from a multiple Tasks running in parallelC# 从并行运行的多个任务更新 UI
【发布时间】:2017-01-26 05:48:43
【问题描述】:

我在 UI 和从任务中更新它时遇到了问题。 在尝试将我的应用程序从 winforms 移植到 UWP 的过程中,我想优化应用程序的 CPU 繁重部分。

以前我使用后台工作程序来运行计算,但是使用任务 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;
                }
            }
        }

【问题讨论】:

  • Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync 吗?
  • 是的:await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =&gt; { }); 我也试过只使用Dispatcher,但结果相似。

标签: c# uwp task


【解决方案1】:

根据我的经验,当 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
    }
}

【讨论】:

  • 这可能是解决方案。我现在实际上使用了一个计时器(100 毫秒)来一次更新所有 UI 元素。您能帮我解决如何设计线程安全类以及“getProgress”方法的外观吗?
  • 您可以创建私有属性进度,get 应该简单地返回该属性。对于线程安全,问题出在集合中,因为进度是原始类型,您可以使用 Interlocked.Exchange(ref progressProperty, value);相反,如果您需要设置一个对象,您应该锁定该对象。
【解决方案2】:

如果您想对集合的每个元素运行相同的操作,我会选择 Parallel.ForEach。

诀窍是在 ForEach 代码中使用 IProgress&lt;T&gt; 向主线程报告更新。 IProgress&lt;T&gt; 构造函数接受一个匿名函数,该函数将在主线程中运行,因此可以更新 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&lt;T&gt; 扩展程序来并行运行可以直接修改 UI 的事件回调。你可以在这里看看:

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

希望对你有帮助!

【讨论】:

    【解决方案3】:

    您可以将函数调用回 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 对于 UWP,试试

    CoreDispatcher dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
    await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
      {
    
      });
    

    代替BeginInvoke;

    【讨论】:

    • 因此在 dna_analyzer_page (UI) 上构造一个“队列”类对象并将其传递给分析器,分析器将数据添加到队列而不是抛出事件。然后在 UI 上使用计时器来BeginInvoke(mI) 数据,就像你的例子一样? MethodInvoker mI = () =&gt; {label.text = queue.someproperty etc.};
    • 是的 - 如果您的所有更新都是基于文本的,那么您可以使用队列。如果它们包含不同的更新(如 textbox.text 和 progressbar.value),则创建一个具有与您的控件匹配的属性的类。将值从各种线程提供给属性。也许在那个类中,使用 System.Component.BackgroundWorker 每秒更新一次 UI 屏幕......
    • UWP 好像没有 MethodInvoker 和 BeginInvoke 方法。
    • 错过了那部分 - 尝试 Dispatcher.RunAsync
    • 所以我是否每隔(最好是 100 毫秒)从调度程序触发一个事件到页面,然后从 UI_Update 类中获取信息并设置 UI 元素值(文本、值、列表视图列表内容) ? Threading.Thread 也不可用,但我想await Task.Delay(100) 也会这样做。
    猜你喜欢
    • 2019-03-08
    • 1970-01-01
    • 1970-01-01
    • 2012-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-28
    相关资源
    最近更新 更多