1

必要になる前に、一連のid値(データベースID値など)をフェッチするための最良の方法に関するアドバイスを見つけたいと思っています。一意のID(int)を必要とするクラスがいくつかあります。実行したいのは、次に使用可能なID(クラスごと、サーバーごと)をフェッチし、ローカルでキャッシュできるようにすることです。IDを取得したら、次のIDを準備したいなど。

私がやろうとしていることを示すために、いくつかのコードを作成しました。コードはひどいです(ロックなどが含まれている必要があります)が、私はそれがポイントを理解していると思います。奇数IDを失うことは問題ではありません-重複IDは(問題)です。GetNextIdAsyncの根性に満足しています-procを呼び出します

this.Database.SqlQuery<int>("EXEC EntityNextIdentityValue @Key", 
            new SqlParameter("Key", key))).First();

sp_getapplockを使用して各戻り値が一意(および増分)であることを確認するSQLServerの場合。

static class ClassId
{
    static private Dictionary<string, int> _ids = new Dictionary<string,int>();
    static private Dictionary<string, Thread> _threads = new Dictionary<string,Thread>();

    static ClassId()
    {
        //get the first NextId for all known classes
        StartGetNextId("Class1");
        StartGetNextId("Class2");
        StartGetNextId("Class3");
    }

    static public int NextId(string key)
    {
        //wait for a current call for nextId to finish
        while (_threads.ContainsKey(key)) { }
        //get the current nextId
        int nextId = _ids[key];
        //start the call for the next nextId
        StartGetNextId(key);
        //return the current nextId
        return nextId;
    }

    static private void StartGetNextId(string key)
    {
        _threads.Add(key, new Thread(() => GetNextIdAsync(key)));
        _threads[key].Start();
    }

    static private void GetNextIdAsync(string key)
    {
        //call the long running task to get the next available value
        Thread.Sleep(1000);
        if (_ids.ContainsKey(key)) _ids[key] += 1;
        else _ids.Add(key, 1);
        _threads.Remove(key);
    }
}

私の質問は、必要になる前に必要になる次の値を常に持つための最良の方法は何ですか?クラスはどのように配置する必要があり、ロックはどこに配置する必要がありますか?たとえば、GetNextIdAsync()内のロックは新しいスレッドを追加しますが、それを開始せず、StartGetNextId()を.Start()を呼び出すように変更しますか?

4

3 に答える 3

2

その列を適切にマークして、データベースに ID 値を生成させる必要があります。SCOPE_IDENTITYなどでその値を取得できます。

実装の主な失敗は、NextId でのビジー待機と、複数のスレッドからの Dictionary への同時アクセスです。最も簡単な解決策は、ohadsc が以下に提案するような BlockingCollection を使用することです。データベースがダウンし、それ以上 ID を取得できない場合を予測する必要があります。アプリケーションをデッドロックしたくはありません。そのため、データベースへのアクセスが失敗した場合に通知する ConcellationToken を受け入れる Take() オーバーロードを使用する必要があります。

于 2012-11-08T17:21:05.283 に答える
0

これには a を使用できますBlockingCollection。基本的に、スレッドが新しい ID をバッファーに送り込むようになります。

BlockingCollection<int> _queue = new BlockingCollection<int>(BufferSize);

void Init()
{
    Task.Factory.StartNew(PopulateIdBuffer, TaskCreationOptions.LongRunning);
}

void PopulateIdBuffer()
{
    int id = 0;
    while (true)
    {
        Thread.Sleep(1000); //Simulate long retrieval
        _queue.Add(id++);
    }
}

void SomeMethodThatNeedsId()
{
    var nextId = _queue.Take();
    ....
}
于 2012-11-08T17:48:04.873 に答える
0

これは、生産者と消費者のパターンに適したアプリケーションのようです。

私は次のようなことを考えています:

private ConcurrentDictionary<string, int> _ids;
private ConcurrentDictionary<string, Thread> _threads;
private Task _producer;
private Task _consumer;
private CancellationTokenSource _cancellation;

private void StartProducer()
{
    _producer = Task.Factory.StartNew(() => 
       while (_cancellation.Token.IsCancellationRequested == false)
       {
           _ids.Add(GetNextKeyValuePair());
       }
   )
}

private void StartConsumer()
{
    _consumer = Task.Factory.StartNew(() => 
       while (_cancellation.Token.IsCancellationRequested == false)
       {
           UseNextId(id);
           _ids.Remove(id);
       }
   )
}

指摘すべきいくつかのこと...

まず、おそらくこれはすでにご存知でしょうが、単純な or の代わりに or のようなスレッドセーフなコレクションを使用することが非常に重要ConcurrentDictionaryです。そうしないと、悪いことが起こり、人が死んだり、赤ちゃんが泣いたりします。BlockingCollectionDictonaryList

CancellationTokenSource2 つ目は、サービス プログラミングで私が慣れ親しんでいる基本的なものよりも、もう少し妥協の少ないものが必要になる場合があります。ポイントは、これらをキャンセルして正常にシャットダウンできるようにすることです。

sleep第 3 に、プロセッサーに負荷がかかりすぎないように、そこに s を入れることを検討してください。

これの詳細は、これらのものをどれだけ速く消費できるかではなく、どれだけ速く生成できるかによって異なります. 私のコードでは、消費者がプロデューサーよりもはるかに高速で実行されている場合、消費者が要求する前に必要な ID を取得できるという保証はまったくありません。ただし、これは、この種のデータを同時に準備するための基本的な方法ではありますが、まともな方法です。

于 2012-11-08T17:33:42.403 に答える