簡體   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