使用多线程时,应用程序意外关闭

时间:2018-03-22 16:29:18

标签: c# multithreading blockingcollection

根据此处描述的文章MS Docs,我正在使用BlockingCollection尝试一种非常简单的Producer Consumer方法,以了解多线程。

我的生产者是一个单独的任务,它从XML文件中读取(有大约4000个节点)并将XElement个节点推送到阻塞集合。

我的消费者将有多个线程从阻止集合中读取并根据XElement将文件上传到站点。

这里的问题是每次尝试运行程序时程序都会意外关闭。它击中了制作人Task.Run但在此之后停止。我无法理解原因。难道我做错了什么?它甚至没有击中catch区块。

代码如下:

            BlockingCollection<XElement> collection = new BlockingCollection<XElement>(100);                
            string metadataFilePath = exportLocation + listTitle + "\\Metadata\\" + exportJobId + ".xml";
            //create the producer
            Task.Run(() =>
            {                    
                //Process only the files that have not been uploaded                                   
                XDocument xmlFile = XDocument.Load(metadataFilePath);
                var query = from c in xmlFile.Elements("Items").Elements("Item")
                            where c.Attribute("IsUploaded").Value == "No"
                            select c;
                foreach (var item in query)
                {
                    collection.Add(item);
                }
                collection.CompleteAdding();
            });

            //process consumer
            Parallel.ForEach(collection, (new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = 2 }), (metadata) => {
                ProcessItems();
            });

2 个答案:

答案 0 :(得分:2)

假设您正在尝试运行控制台应用程序,我可以考虑以下问题:

  1. C#中的任务默认为后台主题,即他们无法使应用程序保持活动状态。如果主线程退出是前台线程,则后台线程也将停止执行。
  2. 考虑到#1,您的并行块可能会在生产者线程生成任何数据之前执行,因此程序退出导致后台生成器线程终止好。尝试使用循环内的TryTake()从集合中读取消费者任务,并在程序中添加对Console.ReadLine()的调用,以确保控制台无法在没有用户按Enter的情况下退出。如果您想并行使用,请参阅示例2 here
  3. 您可以看到更多示例here。 尝试在示例代码中注意以下事项:

    1. 使用块使用(BlockingCollection bc = new BlockingCollection())
    2. 在第一个帖子中调用方法 CompleteAdding(),表示该集合不再接受制作人添加的任何项目。一旦添加了所有项目,生产者线程就会调用它。在将一个集合标记为完成以进行添加之后,不允许添加到集合中,并且当集合为空时,尝试从集合中删除将不会等待。 / p>

    3. 第二个示例中的消费者线程使用 TryTake(out result)。 消费者线程启动并尝试取出值。即使生产者线程没有添加任何项目,它也将继续等待,因为收集尚未通过生产者线程调用CompleteAdding()标记为IsAddingCompleted。当集合被标记为IsAddingCompleted且集合为空时,消费者线程将从TryTake获得错误的返回值,即集合的IsCompleted属性变为true,允许消费者线程完成。

    4. 4.拨打 Console.ReadLine(),这样后台线程的任务都不会在没有完成的情况下终止。

      希望这有帮助。

答案 1 :(得分:2)

answer by Nish26对于问题中的问题是正确的。

我建议用Microsoft TPL Dataflow解决你的生产者/消费者问题:

using System.Threading.Tasks.Dataflow;

var parallelBoundedOptions = new ExecutionDataflowBlockOptions
{
    BoundedCapacity = 100,
    MaxDegreeOfParallelism = 2,
};
var uploadItemBlock = new ActionBlock<XElement>(
    item => ProcessItem(item),
    parallelBoundedOptions
);
string metadataFilePath = exportLocation + listTitle + "\\Metadata\\" + exportJobId + ".xml";
XDocument xmlFile = XDocument.Load(metadataFilePath);
var query = from c in xmlFile.Elements("Items").Elements("Item")
            where c.Attribute("IsUploaded").Value == "No"
            select c;
foreach (var item in query)
{
    uploadItemBlock.SendAsync(item).Wait();
}
uploadItemBlock.Complete();
uploadItemBlock.Completion.Wait();

Dataflow可以更轻松地专注于制作和使用这些项目,而不是如何将它们从生产者传递给消费者。

问题中的实际问题是Parallel.Foreach正在使用BlockingCollection<T>.IEnumerable<T>.GetEnumerator而不是BlockingCollection<T>.GetConsumingEnumerable,如下所示:

static void Main()
{
    var collection = new BlockingCollection<int>(100);
    Task.Run(()=>
    {
        foreach (var element in Enumerable.Range(0, 100_000))
        {
            collection.Add(element);
        }
        collection.CompleteAdding();
    });

    Parallel.ForEach(
        collection, 
        new ParallelOptions { MaxDegreeOfParallelism = 2},
        i => Console.WriteLine(i));

    Console.WriteLine("Done");
}

立即打印“完成”

static void Main()
{
    var collection = new BlockingCollection<int>(100);
    Task.Run(()=>
    {
        foreach (var element in Enumerable.Range(0, 100_000))
        {
            collection.Add(element);
        }
        collection.CompleteAdding();
    });

    Parallel.ForEach(
        collection.GetConsumingEnumerable(), 
        new ParallelOptions { MaxDegreeOfParallelism = 2},
        i => Console.WriteLine(i));

    Console.WriteLine("Done");
}

打印所有数字