4

スレッドセーフは、私が書いた単純なアプリやライブラリが通常メインスレッドでのみ実行されるため、または以前に心配する必要があったクラスのプロパティやフィールドを直接変更しないため、私が心配している側面ではありません。

WebClientただし、リモートサーバーから非同期でデータをダウンロードするために使用している個人的なプロジェクトに取り組み始めました。Queue<Uri>データをダウンロードするための一連のURIの事前に作成されたキューを含む があります。

したがって、次のスニペットを検討してください(これは私の実際のコードではありませんが、私が望んでいることは私の質問を示しています:

private WebClient webClient = new WebClient();
private Queue<Uri> requestQueue = new Queue<Uri>();

public Boolean DownloadNextASync()
{
    if (webClient.IsBusy)
        return false;

    if (requestQueue.Count == 0)
        return false

    var uri = requestQueue.Dequeue();

    webClient.DownloadDataASync(uri);

    return true;

}

私が正しく理解している場合、このメソッドはスレッドセーフではありません(このオブジェクトのこの特定のインスタンスが複数のスレッドに認識されていると仮定します)。私の推論は、チェックとメソッド呼び出しwebClientの間の時間にビジーになる可能性があります。また、チェックと次のアイテムがデキューされる までの間に空になる可能性があります。IsBusyDownloadDataASync()requestQueueCount

私の質問は、このタイプの状況を処理してスレッドセーフにするための最良の方法は何ですか?

これは、この特定のメソッドについて、これが実際に問題を引き起こすには非常に不便なタイミングが必要であることに気付いたので、より抽象的な質問です。その場合をカバーするためにtry-catch、両方の部分が適切であるため、メソッドを適切にラップすることができます。例外をスローします。しかし、別のオプションはありますか?lockここでステートメントが適用されますか?

4

3 に答える 3

1

JosephAlbahariによる「ThreadingInC#」を読むことを強くお勧めします。スレッド化への最初の(誤った)冒険に備えて、それを調べましたが、かなり包括的です。

あなたはここでそれを読むことができます:http://www.albahari.com/threading/

于 2012-08-07T17:55:03.573 に答える
1

あなたが提起したスレッドセーフの懸念は両方とも有効です。さらに、WebClientとQueueの両方がスレッドセーフではないことが文書化されています(MSDNドキュメントの下部にあります)。たとえば、2つのスレッドが同時にデキューしている場合、実際にはキューが内部的に不整合になったり、無意味な戻り値が発生したりする可能性があります。たとえば、Dequeue()の実装が次のようなものである場合:

1. var valueToDequeue = this._internalList[this._startPointer];
2. this._startPointer = (this._startPointer + 1) % this._internalList.Count;
3. return valueToDequeue;

そして、2つのスレッドがそれぞれ1行目を実行してから2行目に進むと、両方が同じ値を返します(ここにも他の潜在的な問題があります)。これは必ずしも例外をスローするわけではないため、lockステートメントを使用して、一度に1つのスレッドのみがメソッド内に入ることができることを保証する必要があります。

private readonly object _lock = new object();

...

lock (this._lock) { 
    // body of method
}

他の誰も同期していないことがわかっている場合は、WebClientまたはキューをロックすることもできます。

于 2012-08-11T03:25:22.487 に答える
1

.Net 4.0 を対象としている場合は、Task Parallel Library を使用してヘルプを得ることができます。

var queue = new BlockingCollection<Uri>();
var maxClients = 4;

// Optionally provide another producer/consumer collection for the data
// var data = new BlockingCollection<Tuple<Uri,byte[]>>();

// Optionally implement CancellationTokenSource

var clients = from id in Enumerable.Range(0, maxClients)
              select Task.Factory.StartNew(
    () =>
    {
        var client = new WebClient();
        while (!queue.IsCompleted)
        {
            Uri uri;
            if (queue.TryTake(out uri))
            {
                byte[] datum = client.DownloadData(uri); // already "async"
                // Optionally pass datum along to the other collection
                // or work on it here
            }
            else Thread.SpinWait(100);
        }
    });

// Add URI's to search
// queue.Add(...);

// Notify our clients that we've added all the URI's
queue.CompleteAdding();

// Wait for all of our clients to finish
clients.WaitAll();

このアプローチを進行状況表示に使用TaskCompletionSource<TResult>するには、イベント ベースの並列処理を管理するために使用できます。

public static Task<byte[]> DownloadAsync(Uri uri, Action<double> progress)
{
    var source = new TaskCompletionSource<byte[]>();
    Task.Factory.StartNew(
        () =>
        {
            var client = new WebClient();
            client.DownloadProgressChanged
                += (sender, e) => progress(e.ProgressPercentage);
            client.DownloadDataCompleted
                += (sender, e) =>
                {
                    if (!e.Cancelled)
                    {
                        if (e.Error == null)
                        {
                            source.SetResult((byte[])e.Result);
                        }
                        else
                        {
                            source.SetException(e.Error);
                        }
                    }
                    else
                    {
                        source.SetCanceled();
                    }
               };
        });

    return source.Task;
}

次のように使用します。

// var urls = new List<Uri>(...);
// var progressBar = new ProgressBar();

Task.Factory.StartNew(
    () =>
    {
       foreach (var uri in urls)
       {
           var task = DownloadAsync(
               uri,
               p =>
                   progressBar.Invoke(
                       new MethodInvoker(
                       delegate { progressBar.Value = (int)(100 * p); }))
               );

           // Will Block!
           // data = task.Result;
       } 
    });
于 2012-08-07T17:57:16.810 に答える