0

数日前、並列化のために WPF アプリケーションでタスクの使用を開始しました。私のアプリケーションは、5 秒ごとにいくつかの作業を実行する必要があります。この作業は 4 つのタスクで並列化する必要があります。それに加えて、タスクによる作業中に UI がフリーズするのを避けるために、バックグラウンド ワーカーを実装する必要があります。タスクがどのように機能するかを理解するための例をたくさん見つけました。ただし、タスクがタイマー、バックグラウンド ワーカー、およびもちろんロックと共にどのように機能するかを理解するための簡単な例は見つかりませんでした。私の理解に基づいて簡単な例を書きました。私がそれを正しく行っているかどうかについてアドバイスをください。このようにして、WPF でのマルチタスクについてよりよく理解できるようになります。返信をお待ちしております。

       namespace timer_and_thread
            {
                /// <summary>
                /// Interaction logic for MainWindow.xaml
                /// </summary>
                public partial class MainWindow : Window
                {
                    DispatcherTimer TimerObject;
                    Task[] tasks;
                    readonly object _countLock = new object();
                    int[] Summ = new int[10];
                    int Index = 0;
                    int ThreadsCounter = 0;


                    public MainWindow()
                    {
                        InitializeComponent();
                        TimerObject = new DispatcherTimer();
                        TimerObject.Tick += new EventHandler(timer_Elapsed);
                        TimerObject.Interval = new TimeSpan(0, 0, 5);
                    }


                    // call the method every 5 seconds
                    private void timer_Elapsed(object sender, EventArgs e)
                    {
                        TimerObject.Stop();

                        // 4 - is the tasks' count
                        ThreadsCounter = 4;
                        Index = 0;

                        tasks = new Task[4];
                        tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
                        tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

                         // wait untill all tasks complete
                         while (ThreadsCounter != 0) ;


                         TimerObject.Start();

                    }


                    private void DoSomeLongWork()
                    {
                        while (Index < Summ.Length)
                        {


                            // lock the global variable from accessing by multiple threads at a time
                        int localIndex = Interlocked.Increment(ref Index) - 1;

                            //I wrote rundom number generation just a an example of doing some calculation and getting some result. It can also be some long calculation. 
                            Random rnd = new Random();
                            int someResult = rnd.Next(1, 100000);

                            // lock the global variable (Summ) to give it the result of calculation 
                            lock (_countLock)
                            {
                                Summ[localIndex] = someResult;
                            }      
                        }


                        Interlocked.Decrement(ref ThreadsCounter);
                        return;

                    }

                    // button by which I start the application working
                    private void Start_Button_Click_1(object sender, RoutedEventArgs e)
                    {
                        TimerObject.Start();
                    }

                }
            }

追加の質問が 2 つあります。

  1. バックグラウンド ワーカーの代わりにタスクを使用できますか?

  2. 私が持っているように、スレッドによるグローバル変数へのアクセスを防ぐためにロックが使用されます。ただし、スレッドで UI 要素にアクセスするには、Dispatcher を使用する必要があります。右?

Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
    {

label1.Content = "Some text";
}));
4

2 に答える 2

4

.さえ必要ないかのように私には見えますBackgroundWorker。現在、次のものがあります。

private void timer_Elapsed(object sender, EventArgs e)
{
    TimerObject.Stop();

    BackgroundWorker backgroundWorkerObject = new BackgroundWorker();
    backgroundWorkerObject.DoWork += new DoWorkEventHandler(StartThreads);
    backgroundWorkerObject.RunWorkerAsync();

    TimerObject.Start();
}


private void StartThreads(object sender, DoWorkEventArgs e)
{
    tasks = new Task[4];
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

    // Give the tasks a second to start.
    Thread.Sleep(1000);
}

スレッドを開始し、BackgroundWorker何らかの未知の理由で続行する前に 1 秒待つだけです。BackgroundWorker退出を許可する前に 1 秒待つ特別な理由はありません。

その不要な遅延を取り除くと、コードを次のように変更できます。

private void timer_Elapsed(object sender, EventArgs e)
{
    TimerObject.Stop();

    tasks = new Task[4];
    tasks[0] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[1] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[2] = Task.Factory.StartNew(() => DoSomeLongWork());
    tasks[3] = Task.Factory.StartNew(() => DoSomeLongWork());

    TimerObject.Start();
}

もう 1 つの問題は、タイマー経過イベントが再び発生する前にこれらのタスクが完了しない場合、Tasks配列を上書きし、計算 (Sum特に配列) に両方の計算セットからの情報が含まれることです。ここでグローバル変数を使用すると、スレッドの完了に 5 秒以上かかる場合に問題が発生します。

DoSomeLongWorkあなたの方法は単なるプレースホルダーであり、それを批判するつもりはありません。ただし、共有変数をインクリメントするためにロックする必要はありません。それよりも:

int localIndex;
// lock the global variable from accessing by multiple threads at a time
lock (_countLock)
{
    localIndex = Index;
    Index++;
}

あなたは書ける:

localIndex = Interlocked.Increment(ref Index, 1) - 1;

Interlocked.Increment値をアトミックにインクリメントし、新しい値を返します。

于 2013-08-30T18:44:17.717 に答える
1

8 月 30 日からの質問に答えるには、while ループの代わりに Tasks の ContinueWhenAll 関数を見て、タイマーの開始をそこに移動します。タスクのリストを取得し、すべてのタスクが完了すると、別のタスクを作成して実行します。これにより、UI がフリーズするのを防ぐことができますが、前のタスクの完了後 5 秒ごとではなく、5 秒ごとに実行する必要がある場合は、別のことを調べる必要があるかもしれません。

http://msdn.microsoft.com/en-us/library/dd321473%28v=vs.110%29.aspx

于 2014-09-10T15:13:05.613 に答える