2016-09-18 70 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

希望它能幫助!