4

私はMonoに苦境に陥っています。BackgroundWorkerインスタンスが同時に実行されない、または少なくとも非常に効率的または迅速に実行されない方法または理由を理解しようとしています。なんらかの理由でスレッドの数が制限されているように見えますが、私は Mono 開発者や .NET 開発者ではないので、SO の兄弟たちに翻弄されています。

次の例を検討してください。

using System;
using System.ComponentModel;
using System.Threading;

namespace backgroundworkertest
{
  class MainClass
  {
    public static void foo(object sender, DoWorkEventArgs e) {
      Console.WriteLine("Start");
      Thread.Sleep(500);
      Console.WriteLine("Done");
    }

    public static void Main (string[] args)
    {
      for(int i = 0; i < 6; i++) {
        BackgroundWorker b = new BackgroundWorker();
        b.WorkerReportsProgress = false;
        b.DoWork += new DoWorkEventHandler(foo);
        b.RunWorkerAsync();
      }

      Thread.Sleep(2000);
      Console.WriteLine("Really done");
    }
  }
}

上記を実行すると、次の出力が得られます。

始める
始める
終わり
始める
始める
終わり
始める
終わり
本当にやった

まず、すべての がBackgroundWorker2 秒以内に完了するわけではありません。ループから、6 つのワーカーをインスタンス化していることは明らかですが、「Done」と表示されるのは 3 つだけです。6 つすべてが同時に実行されている場合、これらのワーカーが完了するのに 2 秒あれば十分だと思います。

本当の裏話は、問題なく動作するアプリを持っていることです (BackgroundWorkerバックグラウンドで HTTP 経由で画像をフェッチするかなりの数の を使用します)。ただし、違いは、このアプリが C# .NET で実装されていることです。アプリを移植するときMono では、BackgroundWorkers は一度に 2 つしか実行されていないように見え、結果として HTTP 接続がタイムアウトする傾向があります.これが事実であるかどうかを確認したかったので、上記の例と質問...

何が起きてる?BackgroundWorkerすべてのが同時に実行されないのはなぜですか? 設定する必要があるパラメータはありますか?

4

1 に答える 1

9

Mono の実装について 100% 確信があるわけではありませんが、Microsoft のバージョンについてはかなりよく知っています。

まず、明確にするために、BackgroundWorker 実際には .NET スレッド (ネイティブ スレッドとは異なります) を生成します。これは、ThreadPool.QueueUserWorkItem自分自身を呼び出すのと同等です。これは、この種の動作が見られる理由を理解するのに役立つかもしれない小さな詳細です。.NET のスレッドは、実際にはネイティブ スレッドを表していない可能性があることに注意してください。実際には、6 つの .NET スレッドで実行されているバックグラウンド ワーカー 6 つすべてが 1 ~ 2 のネイティブ スレッドで実行される可能性があります。私のマシンでは、上記のコードで実際に生成されたネイティブスレッドは 3 つだけでした。

BackgroundWorker第 2 に、 new を構築し、デリゲートを設定してから、ワーカーを非同期で実行するのにかかる時間を考慮していません。これには、実際にはかなりコストのかかる操作であるネイティブ スレッドの作成が含まれる場合と含まれない場合があります。タイマーを追加すると、私のテストでは、6 つすべてを作成するのに合計 1 秒強、場合によってはそれより少し長くかかりました。スケジューリングのため、ThreadPool は、すべてのスレッドが飽和/ブロックしていることを検出するまでに数ミリ秒かかる場合があり (原因でThread.Sleep)、新しいスレッドを作成します。つまり、場合によっては、バックグラウンド ワーカーが 0:01:700 頃に開始され、残り 300 ミリ秒しかないにもかかわらず、関数が 500 ミリ秒スリープすることになります。タスクを完了するのに十分な時間がありません。

Mono の実装が Microsoft の実装よりも少し遅いことに異論はないと思います。それが、Microsoft のランタイムで同じ動作が発生する可能性があることをおそらく発見できなかった理由だと確信しています。

疑わしい場合は、自分のコードを責めてください。通常、そこに問題があるからです。操作が完了すると予想される時間だけスリープすることは、何かをテストするための良い方法ではありません。コンピューターで他のプログラムが実行されている場合はどうなりますか? Thread.Sleep他のプロセスのスレッドがあなたのスレッドをプリエンプトした場合、実行が遅くなることはありません。その場合はさらに悪くなります。

メソッドで5 ミリ秒スリープするようにコードを変更しfoo、バックグラウンド ワーカーを作成した後、メイン メソッドで 20 ミリ秒待機する場合、待機したのと同じ比率で、次のことがわかります。

始める
始める
終わり
終わり
始める
始める
本当にやった
何かキーを押すと続行します 。. .

終了したのは 2 つだけで、全員が開始したわけではありません。テストの欠陥を見つけていただければ幸いです。それらは同時に実行されており、おそらくあなたの問題は別の場所にあると思います。バックグラウンドで http 経由で画像を取得しているとします。何かが正しく機能していない場合、実際にランタイムで異常なことが起こっているのは 1% の時間だけであり、残りの 99% の時間はコードです。同期または非同期 I/O を使用していますか? リクエストを処理するスレッドは 1 つだけですか? 私はそこから始めます。

編集:

Microsoft の実装で見つけたものに頼るのではなく、Mono で実際にどのようなパフォーマンスが得られるかを確認することにしました。Monoが実際に6つの背景作業すべてを開始していることを発見した後。そして、それらはすべて 2000 ミリ秒の制限内で完了しました! 念のため、結果を比較してみます。何が起こっているかを示すためにコードを少し変更しましたが、コードをそのまま実行したときに同じ結果が得られたことを保証します。「作成完了...」メッセージは、メイン スレッドが 2000 ミリ秒のスリープに入る直前に出力されます。出力されるタイムスタンプは、バックグラウンド ワーカーが作業メソッドに入った瞬間です。

Windows モノ
作成完了... 作成完了...
00:00:00.0018610 00:00:00.0088775
00:00:00.0019544 開始
開始 00:00:00.5074494
スタート スタート
完了
完了 00:00:00.5615083
00:00:00.5027995 開始
開始 完了
00:00:00.5028008 00:00:01.0082133
スタート スタート
完了 00:00:01.0082900
完了 開始
00:00:01.0025428 完了
開始 00:00:01.0618140
00:00:01.0026164 開始
開始 完了
完了
完了
本当にやった 本当にやった

まったく同じ 2 台のボックス (Dell T3400、同じハードウェア) でテストを実行しました。1 台は Windows 7 x64、もう 1 台は Ubuntu 12.04 x64 です。変わりはない。デバッガーが接続されていない実行可能ファイルのリリースビルドでこれをテストしていることを確認してください。また、同じハードウェアでテストしていることを確認してください。可能性は低いですが、Linux/mono のインストールに問題がある可能性があります (Windows ではなく Linux で mono を実行していると仮定します)。前に言ったように、これらの結果がそれを裏付けていると思いますが、これは問題ではないと思います. 私はあなたのコードのどこかを探します。

于 2012-05-04T02:05:46.380 に答える