2016-05-31 10 views
0

私はストアドアクションの呼び出しリストであるタイマーを持っています。これらのアクションを非同期的に呼び出すようにします。だから、私はCPUバインド操作をタスクにラップし、非同期/動作待ちにしました。ただし、コンボボックスは更新されません。明らかにコンテキストはUIに戻っていませんが、なぜそれを修正するために何をすべきか理解できません。メインフォームでasync/UIスレッドに戻って待たないでください

コード:

public FormMain() 
{ 
    InitializeComponent(); 

    pt = new PeriodicTask(() => Execute()); 
    pt.Start(); 

    actions = new List<ActionWrapper>(); 
    actions.Add(new ActionWrapper() { Periodic = false, MyAction = async() => { 
     bool b = await NetworkOperation(); 
     comboBoxPairs.DataSource = pairs; // this doesn't update combo box 
     comboBoxPairs.DisplayMember = "Name"; 
     //comboBoxPairs.Refresh(); // this is even crashing app 
    }}); 
} 

private Task<bool> NetworkOperation() 
{ 
    return Task.Run(() => { 

     // CPU bound activity goes here 
     return true; 
    }); 
} 

private void Execute() 
{ 
    Parallel.ForEach(actions, 
     new ParallelOptions { MaxDegreeOfParallelism = 10 }, 
     x => { 
      x.MyAction(); 
      if (!x.Periodic) 
       actions.Remove(x); 
     }); 
} 

Timerクラス:

public class PeriodicTask 
{ 
    private System.Threading.Timer timer; 
    private int dueTime; 
    private int periodTime; 
    private Action callBack; 

    public PeriodicTask(Action cb) 
    { 
     callBack = cb; 
     timer = new System.Threading.Timer(Task, null, Timeout.Infinite, Timeout.Infinite); 
     dueTime = 100; 
     periodTime = 5000; 
    } 

    public void Start() 
    { 
     timer.Change(dueTime, periodTime); 
    } 

    public void Stop() 
    { 
     timer.Change(Timeout.Infinite, Timeout.Infinite); 
    } 

    private void Task(object parameter) 
    { 
     callBack(); 
    } 
} 

これは私がアクションを保持するために使用するラッパークラスです:

public class ActionWrapper 
{ 
    public bool Periodic { get; set; } 
    public Func<Task> MyAction { get; set; } 
} 
+1

問題は、アクションが任意のスレッドプールスレッドで実行され、WinForms同期コンテキストからの非同期アクションをトリガーしていないことです。待機は、継続が同じ同期コンテキストに戻るという点で機能しますが、Parallel.ForEachはデフォルトのスケジューラ(プール)でタスクをキューに入れるだけなので、WinFormsではありません。また、ネットワーク通話が最初に終了した場合、まだ作成していないため、行が並んでいないという競合条件があります。スリムチャンスは、与えられたが、まだ奇妙に見えます。実は、レースコンディションについては少し無視してください。 –

+0

私は間違っていると思う、コードの少しを誤解していた。 –

+0

'TaskScheduler.FromCurrentSynchronizationContext()'をアクションに渡す必要がありますか?しかし、私はasync/awaitを使用することはできませんし、コンテキストをTPLで自分で変更する必要があります。 – Pablo

答えて

1

それはそうでないということではありませんスイッチバック、System.Threading.Timerを使用しているため、UIスレッドで最初に起動しないWPFコンテキストのうち、スレッドプールスレッド上のティックを処理します。

DispatcherTimerに置き換えることができます。

System.Timers.TimerがWPFアプリケーションで使用されている場合は、System.Timers.Timerが別のスレッドとユーザーインターフェイス(UI)スレッドで実行されることに注意してください。ユーザーインターフェイス(UI)スレッド上のオブジェクトにアクセスするには、InvokeまたはBeginInvokeを使用して、ユーザーインターフェイス(UI)スレッドのディスパッチャに操作を送信する必要があります。 System.Timers.Timerとは逆のDispatcherTimerを使用する理由は、DispatcherTimerがDispatcherと同じスレッドで実行され、DispatcherPriorityがDispatcherTimerで設定できるということです。

また、System.Threading.Timerと同じように、再入場に対処する必要があります。なぜなら、CPUによるバウンド操作でも以前のチックを処理できるからです。

using System; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Threading; 

namespace WpfApplication1 
{ 
    public partial class MainWindow : Window 
    { 
     DispatcherTimer timer = new DispatcherTimer(); 

     long currentlyRunningTasksCount; 

     public MainWindow() 
     { 
      InitializeComponent(); 

      Loaded += MainWindow_Loaded; 

      timer.Interval = TimeSpan.FromSeconds(1); 

      timer.Tick += async (s, e) => 
      { 
       // Prevent re-entrance. 
       // Skip the current tick if a previous one is already in processing. 
       if (Interlocked.CompareExchange(ref currentlyRunningTasksCount, 1, 0) != 0) 
       { 
        return; 
       } 
       try 
       { 
        await ProcessTasks(); 
       } 
       finally 
       { 
        Interlocked.Decrement(ref currentlyRunningTasksCount); 
       } 
      }; 
     } 

     private void MainWindow_Loaded(object sender, RoutedEventArgs e) 
     { 
      // This one would crash, ItemsSource requires to be invoked from the UI thread. 
      // ThreadPool.QueueUserWorkItem(o => { listView.Items.Add("started"); }); 

      listView.Items.Add("started"); 

      timer.Start(); 
     } 

     async Task ProcessTasks() 
     { 
      var computed = await Task.Run(() => CpuBoundComputation()); 

      listView.Items.Add(string.Format("tick processed on {0} threads", computed.ToString())); 
     } 

     /// <summary> 
     /// Computes Cpu-bound tasks. From here and downstream, don't try to interact with the UI. 
     /// </summary> 
     /// <returns>Returns the degree of parallelism achieved.</returns> 
     int CpuBoundComputation() 
     { 
      long concurrentWorkers = 0; 

      return 
       Enumerable.Range(0, 1000) 
       .AsParallel() 
       .WithDegreeOfParallelism(Math.Max(1, Environment.ProcessorCount - 1)) 
       .Select(i => 
       { 
        var cur = Interlocked.Increment(ref concurrentWorkers); 

        SimulateExpensiveOne(); 

        Interlocked.Decrement(ref concurrentWorkers); 
        return (int)cur; 
       }) 
       .Max(); 
     } 

     /// <summary> 
     /// Simulate expensive computation. 
     /// </summary> 
     void SimulateExpensiveOne() 
     { 
      // Prevent from optimizing out the unneeded result with GC.KeepAlive(). 
      GC.KeepAlive(Enumerable.Range(0, 1000000).Select(i => (long)i).Sum()); 
     } 
    } 
} 

あなたは何が起こっているかのprecise制御が必要な場合は、イベントをキューイングし、独立して処理して表示するとオフに優れている:

using System; 
using System.Collections.Concurrent; 
using System.ComponentModel; 
using System.Linq; 
using System.Threading; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Threading; 

namespace WpfApplication2 
{ 
    public partial class MainWindow : Window 
    { 
     DispatcherTimer fastTimer = new DispatcherTimer(); 

     BackgroundProcessing processing = new BackgroundProcessing(); 

     public MainWindow() 
     { 
      InitializeComponent(); 

      processing.Start(); 

      fastTimer.Interval = TimeSpan.FromMilliseconds(10); 
      fastTimer.Tick += Timer_Tick; 

      fastTimer.Start(); 
     } 

     private void Timer_Tick(object sender, EventArgs e) 
     { 
      Notification notification; 
      while ((notification = processing.TryDequeue()) != null) 
      { 
       listView.Items.Add(new { notification.What, notification.HappenedAt, notification.AttributedToATickOf }); 
      }   
     } 

     protected override void OnClosing(CancelEventArgs e) 
     { 
      base.OnClosing(e); 
      processing.Stop(); 
     } 
    } 

    public class Notification 
    { 
     public string What { get; private set; } 

     public DateTime AttributedToATickOf { get; private set; } 

     public DateTime HappenedAt { get; private set; } 

     public Notification(string what, DateTime happenedAt, DateTime attributedToATickOf) 
     { 
      What = what; 
      HappenedAt = happenedAt; 
      AttributedToATickOf = attributedToATickOf; 
     } 
    } 

    public class BackgroundProcessing 
    { 
     /// <summary> 
     /// Different kind of timer, <see cref="System.Threading.Timer"/> 
     /// </summary> 
     Timer preciseTimer; 

     ConcurrentQueue<Notification> notifications = new ConcurrentQueue<Notification>(); 

     public Notification TryDequeue() 
     { 
      Notification token; 
      notifications.TryDequeue(out token); 
      return token; 
     } 

     public void Start() 
     { 
      preciseTimer = new Timer(o => 
      { 
       var attributedToATickOf = DateTime.Now; 

       var r = new Random(); 

       Parallel.ForEach(Enumerable.Range(0, 2), i => { 

        Thread.Sleep(r.Next(10, 5000)); 

        var happenedAt = DateTime.Now; 

        notifications.Enqueue(
         new Notification("Successfully loaded cpu 100%", happenedAt, attributedToATickOf)); 
       }); 

      }, null, 0, 1000); 
     } 

     public void Stop() 
     { 
      preciseTimer.Change(0, 0); 
     } 
    } 
} 

UPDATE:Windowsの はあなたとDispatcherTimerを置き換えることができますフォーム2番目のコードサンプルではSystem.Windows.Forms.Timerです。

+0

残念ながら、WPFアプリケーションではありませんが、タグで言及したようにWindowsフォームです。 – Pablo

+0

次に、Form.Loadイベントから開始し、2番目の例に示すようにキューでポーリングするTask.Delay(10)を待つ無限ループを使用できます。 –

+1

また、DispatcherTimerを[System.Windows.Forms.Timer](https://msdn.microsoft.com/en-us/library/system.windows.forms.timer)に置き換えることもできます(v = vs。 110).aspx) –

関連する問題