簡體   English   中英

異步生產者/消費者

[英]Async Producer/Consumer

我有一個從幾個線程訪問的類的實例。 此類接受此調用並將元組添加到數據庫中。 我需要以串行方式完成此操作,因為由於某些db約束,並行線程可能導致數據庫不一致。

由於我是C#中並行性和並發性的新手,我這樣做了:

private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();

public void AddDData(string info)
{
    Task t = new Task(() => { InsertDataIntoBase(info); });
    _tasks.Add(t);
}

private void InsertWorker()
{
    Task.Factory.StartNew(() =>
    {
        while (!_tasks.IsCompleted)
        {
            Task t;
            if (_tasks.TryTake(out t))
            {
                t.Start();
                t.Wait();
            }
        }
    });
}

AddDData是由多個線程調用的,而InsertDataIntoBase是一個非常簡單的插入,應該需要幾毫秒。

問題在於,由於某種原因,我的知識缺乏使我無法弄清楚,有時候任務被調用兩次! 它總是這樣:

T1 T2 T3 T1 < - PK錯誤。 T4 ......

我明白了.Take()完全錯了,我錯過了什么,或者我的生產者/消費者實施真的很糟糕?

最誠摯的問候,拉斐爾

更新:

正如所建議的那樣,我使用這種架構進行了快速沙盒測試實現,正如我懷疑的那樣,它並不能保證在前一個任務完成之前不會觸發任務。

在此輸入圖像描述

所以問題仍然存在:如何正確排隊任務並按順序啟動它們?

更新2:

我簡化了代碼:

private BlockingCollection<Data> _tasks = new BlockingCollection<Data>();

public void AddDData(Data info)
{
    _tasks.Add(info);
}

private void InsertWorker()
{
    Task.Factory.StartNew(() =>
    {
        while (!_tasks.IsCompleted)
        {
            Data info;
            if (_tasks.TryTake(out info))
            {
                InsertIntoDB(info);
            }
        }
    });
}

注意我擺脫了任務,因為我依賴於同步的InsertIntoDB調用(因為它在循環中),但仍然沒有運氣......這一代很好,我絕對相信只有唯一的實例會進入隊列。 但無論我嘗試,有時同一個對象被使用兩次。

我認為這應該有效:

    private static BlockingCollection<string> _itemsToProcess = new BlockingCollection<string>();

    static void Main(string[] args)
    {
        InsertWorker();
        GenerateItems(10, 1000);
        _itemsToProcess.CompleteAdding();
    }

    private static void InsertWorker()
    {
        Task.Factory.StartNew(() =>
        {
            while (!_itemsToProcess.IsCompleted)
            {
                string t;
                if (_itemsToProcess.TryTake(out t))
                {
                    // Do whatever needs doing here
                    // Order should be guaranteed since BlockingCollection 
                    // uses a ConcurrentQueue as a backing store by default.
                    // http://msdn.microsoft.com/en-us/library/dd287184.aspx#remarksToggle
                    Console.WriteLine(t);
                }
            }
        });
    }

    private static void GenerateItems(int count, int maxDelayInMs)
    {
        Random r = new Random();
        string[] items = new string[count];

        for (int i = 0; i < count; i++)
        {
            items[i] = i.ToString();
        }

        // Simulate many threads adding items to the collection
        items
            .AsParallel()
            .WithDegreeOfParallelism(4)
            .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
            .Select((x) =>
            {
                Thread.Sleep(r.Next(maxDelayInMs));
                _itemsToProcess.Add(x);
                return x;
            }).ToList();
    }

這確實意味着使用者是單線程的,但允許多個生產者線程。

從你的評論

“我簡化了此處顯示的代碼,因為數據不是字符串”

我假設傳遞給AddDData的info參數是一個可變的引用類型。 確保調用者沒有為多個調用使用相同的info實例,因為該引用是在任務lambda中捕獲的。

根據您提供的跟蹤,唯一合乎邏輯的可能性是您已將InsertWorker調用兩次(或更多次)。 因此,有兩個后台線程等待項目出現在集合中,偶爾它們都設法抓取一個項目並開始執行它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM