1

この点について多くの回答を読みましたが、まだ解決策が見つかりませんでした。

キャッシュされた値に問題があるカウンター属性を持つクラスがあります。volatile でも動作しないようです:

public class MyClass {
    private Timer _timer;
    private int _threadsCounter = 0;
    public StreamWriter Tracer { get; set; }

    public MyClass() {
        _timer = new Timer(1000.0 * 10);
        _timer.AutoReset = true;
        _timer.Elapsed += new ElapsedEventHandler(OnTimer);
        _timer.Start();
    }

    private void OnTimer(object sender, ElapsedEventArgs e) {
        HashSet<Task> taskPool = new HashSet<Task>();
        try {
            if (Tracer != null) Tracer.WriteLine("[{0}] onTimer start. Current threads counter is {1}.", DateTime.Now, _threadsCounter);
            if (_threadsCounter >= 10) return;

            // create parallel tasks
            for (int i = 0; i < 8; i++) {
                // limit on the max num of parallel processing but the counter remains unchanged during this timer event!!!
                if (_threadsCounter >= 10) break;

                var timeout = (30 + i * 2);
                var task = Task.Run(() => {
                        var localCounter = System.Threading.Interlocked.Increment(ref _threadsCounter);
                        try {
                            System.Threading.Thread.Sleep(timeout * 1000);
                        }
                        finally {
                            System.Threading.Interlocked.Decrement(ref _threadsCounter);
                        }
                    });
                taskPool.Add(task);
            }

        }
        finally {
            if (Tracer != null) 
                Tracer.WriteLine("[{0}] onTimer end. Created {1} tasks. Current threads counter is {2}.", DateTime.Now, taskPool.Count, _threadsCounter);
        }
    }

出力が次のように、onTimer が _threadsCounter 変数をキャッシュしているようです。

[14:10:47] onTimer start. Current threads counter is 0.
[14:10:47] onTimer end. Created 8 tasks. Current threads counter is 0.

[14:10:57] onTimer start. Current threads counter is 8.
[14:10:57] onTimer end. Created 8 tasks. Current threads counter is 8.

[14:11:07] onTimer start. Current threads counter is 16.
[14:11:07] onTimer end. Created 0 tasks. Current threads counter is 16.

[14:11:17] onTimer start. Current threads counter is 15.
[14:11:17] onTimer end. Created 0 tasks. Current threads counter is 15.

[14:11:37] onTimer start. Current threads counter is 4.
[14:11:37] onTimer end. Created 8 tasks. Current threads counter is 4.

[14:11:47] onTimer start. Current threads counter is 8.
[14:11:47] onTimer end. Created 8 tasks. Current threads counter is 8.

[14:11:57] onTimer start. Current threads counter is 16.
[14:11:57] onTimer end. Created 0 tasks. Current threads counter is 16.

なぜ16に到着するのですか?コードを少し変更して問題を解決しました。

var localCounter = _threadsCounter;
...
if ((localCounter + taskPool.Count) >= 10) break;

しかし、なぜこの動作ですか?

4

2 に答える 2

4

Task.Runすぐにタスクを開始しません。タスクをスレッド プール キューに追加して戻ります。

あなたの場合、新しいタスクの実行が開始される前にループ全体forが実行されるため、何も変わりません_threadsCounter。だからこそvolatile役に立たない。

于 2015-11-05T13:46:25.447 に答える
1

実際に開始され、カウンターをインクリメントするまでに到達したタスクの数を効果的にテストしています。これには少し時間がかかります。つまり、基本的には 8 つのタスクすべてを作成して開始し、カウンターインクリメントします。この時点では、10 を超えていることに気付くには遅すぎます。

より良い解決策は、タスクを開始する前にカウンターをインクリメントすることです。

// Increment the counter in expectation of starting a task
var localCounter = Interlocked.Increment(ref _threadsCounter);
if (localCounter >= 10)
{
    // Ah, we're not going to start a task after all, so undo
    // the increment
    Interlocked.Decrement(ref _threadsCounter);
    break;
}
else 
{
    // Start a task, which will decrement the counter at the end.
    // (You could add the "decrement" bit as a continuation, even...)
}
于 2015-11-05T13:46:41.093 に答える