繁体   English   中英

C#从并行运行的多个任务更新UI

[英]C# Updating UI from a multiple Tasks running in parallel

我有关于UI的问题并从任务中更新它。 在尝试将我的应用程序从winforms移植到UWP时,我想在这个过程中优化CPU的重要部分。

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

您可以将函数调用回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;

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

如果你想为集合的每个元素运行相同的动作,我会选择Para​​llel.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

希望能帮助到你!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM