web-dev-qa-db-ja.com

ConcurrentQueue <T>でスレッドを処理する方法

私はキューを操作する最良の方法が何であるかを理解しようとしています。 DataTableを返すプロセスがあります。次に、各DataTableが前のDataTableとマージされます。 1つの問題があり、最終的なBulkCopy(OutOfMemory)まで保持するにはレコードが多すぎます。

そのため、受信した各DataTableをすぐに処理する必要があると判断しました。 _ConcurrentQueue<T>_について考えていますが、WriteQueuedData()メソッドがテーブルをデキューしてデータベースに書き込む方法がわかりません。

例えば:

_public class TableTransporter
{
    private ConcurrentQueue<DataTable> tableQueue = new ConcurrentQueue<DataTable>();

    public TableTransporter()
    {
        tableQueue.OnItemQueued += new EventHandler(WriteQueuedData);   // no events available
    }

    public void ExtractData()
    {
        DataTable table;

        // perform data extraction
        tableQueue.Enqueue(table);
    }

    private void WriteQueuedData(object sender, EventArgs e)
    {
        BulkCopy(e.Table);
    }
}
_

私の最初の質問は、サブスクライブするイベントが実際にはないという事実は別として、ExtractData()を非同期で呼び出す場合、これで十分でしょうか?第二に、_ConcurrentQueue<T>_関数の方法について何か不足していて、キューに入れられたオブジェクトと非同期で動作するために何らかの形のトリガーが必要ですか?

pdate OnItemQueuedイベントハンドラーを持つ_ConcurrentQueue<T>_からクラスを派生させました。次に:

_new public void Enqueue (DataTable Table)
{
    base.Enqueue(Table);
    OnTableQueued(new TableQueuedEventArgs(Table));
}

public void OnTableQueued(TableQueuedEventArgs table)
{
    EventHandler<TableQueuedEventArgs> handler = TableQueued;

    if (handler != null)
    {
        handler(this, table);
    }
}
_

この実装に関する懸念はありますか?

19
IAbstract

問題の私の理解から、あなたはいくつかのことを逃しています。

コンカレントキューは、データ構造を明示的にロックする必要なく、キューに対する複数のスレッドの読み取りと書き込みを受け入れるように設計されたデータ構造です。 (ジャズはすべて舞台裏で処理されます。またはコレクションは、ロックを取得する必要がないように実装されています。)

それを念頭に置いて、あなたが使用しようとしているパターンは「生産/消費者」であるように見えます。最初に、作業を作成する(およびキューに項目を追加する)いくつかのタスクがあります。次に、キューからアイテムを消費する(およびアイテムをデキューする)という2番目のタスクがあります。

したがって、実際には2つのスレッドが必要です。1つはアイテムの追加、もう1つはアイテムの削除です。並行コレクションを使用しているため、アイテムを追加する複数のスレッドとアイテムを削除する複数のスレッドを使用できます。ただし、コンカレントキューで競合が増えるほど、ボトルネックになるのが早くなります。

24
Chris Smith

ConcurrentQueueが役立つのはごく少数の場合だけだと思います。その主な利点は、ロックフリーであることです。ただし、通常、プロデューサースレッドは、処理できるデータがあることをなんとかしてコンシューマースレッドに通知する必要があります。スレッド間のこのシグナリングはロックを必要とし、ConcurrentQueueを使用する利点を打ち消します。スレッドを同期する最も速い方法は、ロック内でのみ機能するMonitor.Pulse()を使用することです。他のすべての同期ツールはさらに低速です。

もちろん、コンシューマーは、キューに何かが存在するかどうかを継続的に確認することができます。少し良いのは、コンシューマがチェックの間に待つ場合です。

キューに書き込むときにスレッドを上げることは非常に悪い考えです。 ConcurrentQueueを使用しておそらく1マイクロ秒を節約することは、eventhandlerを実行することによって完全に無駄になります。

すべての処理がイベントハンドラーまたは非同期呼び出しで行われる場合、問題はなぜまだキューが必要なのかということです。データをハンドラーに直接渡し、キューをまったく使用しない方がよいでしょう。

ConcurrentQueueの実装は、同時実行を可能にするためにかなり複雑であることに注意してください。ほとんどの場合、通常のQueue<>とキューへのすべてのアクセスをロックします。キューへのアクセスに必要なのはマイクロ秒だけなので、2つのスレッドが同じマイクロ秒でキューにアクセスすることはほとんどなく、ロックのために遅延が生じることはほとんどありません。通常のQueue<>ロックを使用すると、多くの場合ConcurrentQueueよりもコードの実行が速くなります。

18
Peter Huber

これは私が思いついた完全な解決策です:

public class TableTransporter
{
    private static int _indexer;

    private CustomQueue tableQueue = new CustomQueue();
    private Func<DataTable, String> RunPostProcess;
    private string filename;

    public TableTransporter()
    {
        RunPostProcess = new Func<DataTable, String>(SerializeTable);
        tableQueue.TableQueued += new EventHandler<TableQueuedEventArgs>(tableQueue_TableQueued);
    }

    void tableQueue_TableQueued(object sender, TableQueuedEventArgs e)
    {
        //  do something with table
        //  I can't figure out is how to pass custom object in 3rd parameter
        RunPostProcess.BeginInvoke(e.Table,new AsyncCallback(PostComplete), filename);
    }

    public void ExtractData()
    {
        // perform data extraction
        tableQueue.Enqueue(MakeTable());
        Console.WriteLine("Table count [{0}]", tableQueue.Count);
    }

    private DataTable MakeTable()
    { return new DataTable(String.Format("Table{0}", _indexer++)); }

    private string SerializeTable(DataTable Table)
    {
        string file = Table.TableName + ".xml";

        DataSet dataSet = new DataSet(Table.TableName);

        dataSet.Tables.Add(Table);

        Console.WriteLine("[{0}]Writing {1}", Thread.CurrentThread.ManagedThreadId, file);
        string xmlstream = String.Empty;

        using (MemoryStream memstream = new MemoryStream())
        {
            XmlSerializer xmlSerializer = new XmlSerializer(typeof(DataSet));
            XmlTextWriter xmlWriter = new XmlTextWriter(memstream, Encoding.UTF8);

            xmlSerializer.Serialize(xmlWriter, dataSet);
            xmlstream = UTF8ByteArrayToString(((MemoryStream)xmlWriter.BaseStream).ToArray());

            using (var fileStream = new FileStream(file, FileMode.Create))
                fileStream.Write(StringToUTF8ByteArray(xmlstream), 0, xmlstream.Length + 2);
        }
        filename = file;

        return file;
    }

    private void PostComplete(IAsyncResult iasResult)
    {
        string file = (string)iasResult.AsyncState;
        Console.WriteLine("[{0}]Completed: {1}", Thread.CurrentThread.ManagedThreadId, file);

        RunPostProcess.EndInvoke(iasResult);
    }

    public static String UTF8ByteArrayToString(Byte[] ArrBytes)
    { return new UTF8Encoding().GetString(ArrBytes); }

    public static Byte[] StringToUTF8ByteArray(String XmlString)
    { return new UTF8Encoding().GetBytes(XmlString); }
}

public sealed class CustomQueue : ConcurrentQueue<DataTable>
{
    public event EventHandler<TableQueuedEventArgs> TableQueued;

    public CustomQueue()
    { }
    public CustomQueue(IEnumerable<DataTable> TableCollection)
        : base(TableCollection)
    { }

    new public void Enqueue (DataTable Table)
    {
        base.Enqueue(Table);
        OnTableQueued(new TableQueuedEventArgs(Table));
    }

    public void OnTableQueued(TableQueuedEventArgs table)
    {
        EventHandler<TableQueuedEventArgs> handler = TableQueued;

        if (handler != null)
        {
            handler(this, table);
        }
    }
}

public class TableQueuedEventArgs : EventArgs
{
    #region Fields
    #endregion

    #region Init
    public TableQueuedEventArgs(DataTable Table)
    {this.Table = Table;}
    #endregion

    #region Functions
    #endregion

    #region Properties
    public DataTable Table
    {get;set;}
    #endregion
}

概念の証明として、それはかなりうまくいくようです。最大で4つのワーカースレッドが表示されました。

3
IAbstract