1

私の記事サイトには、DB の更新を必要とするいくつかの統計操作があります。これをスレッドと非同期で行いたいと考えています。

問題は、ここで提案されていることを暗示しようとしているときです: C# でメソッドを起動して忘れる最も簡単な方法は?


ThreadPool.QueueUserWorkItem(o => FireAway());

FireAway() にはデータベースの更新が含まれているため、「データベース接続が既に開いています」というエラーが表示されます。

私の質問は、バックグラウンドでアトミック操作として衝突することなく動作するスレッドを作成する最も簡単な方法は何ですか。

私のサイト: www.mentallica.co.il

追加情報..... FireAway() には、すべてのデータベース更新を格納する dll への呼び出しが含まれています。

bll MyBuisnessLogic bll = new MyBuisnessLogic() という共有クラスを使用しています

そして内部 FireAway()

bll.RunStatistics();

bll.RunStatistics() 関数は、SQL 接続を開いたり閉じたりします。

あるスレッドがSQL接続を開き、別のスレッドがすでに開いている接続を開こうとすると、問題が発生すると思います。

たぶん、新しいスレッド用に MyBuisnessLogic の別のインスタンスを作成する必要がありますか? たぶん、 () を使用して内部でこれを行う必要がありますか? (MyBuisnessLogic bll = new MyBuisnessLogic) を使用するようなものですか?

---- 確認したところ、MyBuisnessLogic が機能するには Idisposible が必要であることがわかりました。

4

1 に答える 1

1

ノンブロッキング Db 処理が必要な一般的な理由は次のとおりです。

  1. 各データベース操作に時間がかかるため
  2. 処理するデータが多く、並列処理によってスループットが向上する可能性があるためです。(つまり、ボトルネックがデータベースに移動するだけではないと想定しています)

あなたの投稿が示唆するようSqlConnectionsに、共有SqlConnectionSqlCommandスレッド間でのリソースの管理は良い考えではないため、考慮が必要です。へのアクセスを同期することSqlConnectionは、並列化の利点を台無しにするため、望ましくありません。

問題 1. に対する簡単な解決策は、各スレッドに独自の を強制的に確立させることですがSqlConnection、これは高いデータベース スループットにはつながりません。

        Task.Factory.StartNew(() =>
        {
            using (var conn = new SqlConnection(connectionString))
            using (var cmd = conn.CreateCommand())
            {
                conn.Open();
                SetupCmd(cmd);
                SaveStat(cmd, statToSave);
            }
        });

バックグラウンド書き込みの代替手段が存在します (ケース 1.)。たとえば、1 つ以上の寿命の長いライター スレッドを持ち、キューをリッスンConcurrentQueueます。ライタースレッドは、全体を通して単一のオープンを維持します。BlockingCollectionGetConsumingEnumerableSqlConnection

2. のような大量の状況では、SqlConnectionとの再利用はSqlCommandsパフォーマンスに不可欠です。次に、データを複数のスレッド (TPL を使用している場合はタスク) に分割する必要があります。Parallel.ForEachがほとんどの面倒な作業を行ってくれます。以下では、オーバーロード withを使用してandlocalInitを確立し、それを各本体に渡し、タスクの最後に を呼び出します (の 0..N 回の反復の後)。 Body - デフォルトのパーティショナーが使用されるため、TPL は、必要なタスクの数と、各タスクの本体に渡される項目の数を決定します)。これにより、スレッド ローカル ストレージの使用と同様のパラダイムが可能になります。SqlConnectionSqlCommandlocalFinallylocalInit

1 つの注意点 - 処理が大量の挿入操作のみの場合、SqlBulkCopyの方が全体的に優れたアプローチになる可能性があります。

TPL を使用したいくつかのオプションを次に示します。

create table StatsData
(
    x int ,
    y decimal(20,5),
    name nvarchar(50)
)

そしてモデル:

public class StatsData
{
    public int X { get; private set; }
    public double Y { get; private set; }
    public string Name { get; private set; }
    public StatsData(int x, double y, string name)
    {
        X = x;
        Y = y;
        Name = name;
    }
}

次のクラスは、2 つの非同期オプションを提供します (ポイント 1 と 2 用)。

public class Dispatcher
{
    // Helpers - refactoring
    private static void SetupCmd(SqlCommand cmd)
    {
        cmd.CommandText = "insert into dbo.statsdata(x, y, Name) values (@x, @y, @Name);";
        cmd.CommandType = CommandType.Text;
        cmd.Parameters.Add("@x", SqlDbType.Int);
        cmd.Parameters.Add("@y", SqlDbType.Decimal);
        cmd.Parameters.Add("@Name", SqlDbType.NVarChar, 30);
    }

    private static void SaveStat(SqlCommand cmd, StatsData statToSave)
    {
        cmd.Parameters["@x"].Value = statToSave.X;
        cmd.Parameters["@y"].Value = statToSave.Y;
        cmd.Parameters["@Name"].Value = statToSave.Name;
        cmd.ExecuteNonQuery();
    }

    // 1. Save 1 stat at a time on a background task. Use for low / intermittent volumes 
    public void SaveStatAsynch(string connectionString, StatsData statToSave)
    {
        Task.Factory.StartNew(() =>
        {
            using (var conn = new SqlConnection(connectionString))
            using (var cmd = conn.CreateCommand())
            {
                conn.Open();
                SetupCmd(cmd);
                SaveStat(cmd, statToSave);
            }
        });
    }

    // 2. For background writing of large volumes of stats. Uses the default partitioner in parallel foreach
    public void SaveStatsParallel(string connectionString, IEnumerable<StatsData> statsToSave)
    {
        Parallel.ForEach(
            statsToSave,
            // localInit. Return value is passed to each body invocation
            () =>
                {
                    var conn = new SqlConnection(connectionString);
                    var cmd = conn.CreateCommand();
                    SetupCmd(cmd);
                    conn.Open();
                    return new
                    {
                        Conn = conn,
                        Cmd = cmd
                    };
                },
            // Body, 0..N per Task decided by TPL
            (stat, loopState, initData) =>
                {
                    SaveStat(initData.Cmd, stat);
                    return initData;
                },
            // Disposables
            (initData) =>
                {
                    initData.Cmd.Dispose();
                    initData.Conn.Dispose();
                }
            );
    }

使用例:

        const string connString = @"Server=.\SqlExpress;DataBase=StatsDb;Integrated Security=true";
        // Create some dummy data
        var statsToSave = 
            Enumerable
                .Range(0, 10000)
                .Select(i => new StatsData(i, i*Math.PI, string.Format("Stat #{0}", i)));
        // Insert this in parallel on background tasks / threads as determined by the TPL
        new Dispatcher().SaveStatsParallel(connString, statsToSave);
于 2013-09-21T10:27:37.693 に答える