c# - 在 C# 中实现生产者/消费者模式

标签 c# .net events design-patterns producer-consumer

如何使用事件和委托(delegate)在 C# 中实现生产者/消费者模式?使用这些设计模式时,在资源方面需要注意什么?是否有任何我需要注意的边缘情况?

最佳答案

我知道这个帖子有点老了,但由于我有时会在搜索中遇到它,所以我决定将这个生产者-消费者代码分享给那些想知道如何实现简单的通用生产者-消费者作业队列的人。

Job 类用于以委托(delegate)的形式“存储”对象的方法调用。然后在处理作业时调用委托(delegate)。任何相关的参数也存储在这个 Job 类中。

通过这个简单的模式,可以在入队和出队过程中实现多线程。实际上这只是最简单的部分:多线程给您的代码带来了新的挑战,稍后您会注意到它们;-)

我最初在这个 thread 中发布了这段代码.

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;

// Compiled and tested in: Visual Studio 2017, DotNET 4.6.1

namespace MyNamespace
{
    public class Program
    {
        public static void Main(string[] args)
        {
            MyApplication app = new MyApplication();
            app.Run();
        }
    }

    public class MyApplication
    {
        private BlockingCollection<Job> JobQueue = new BlockingCollection<Job>();
        private CancellationTokenSource JobCancellationTokenSource = new CancellationTokenSource();
        private CancellationToken JobCancellationToken;
        private Timer Timer;
        private Thread UserInputThread;



        public void Run()
        {
            // Give a name to the main thread:
            Thread.CurrentThread.Name = "Main";

            // Fires a Timer thread:
            Timer = new Timer(new TimerCallback(TimerCallback), null, 1000, 2000);

            // Fires a thread to read user inputs:
            UserInputThread = new Thread(new ThreadStart(ReadUserInputs))
            {
                Name = "UserInputs",
                IsBackground = true
            };
            UserInputThread.Start();

            // Prepares a token to cancel the job queue:
            JobCancellationToken = JobCancellationTokenSource.Token;

            // Start processing jobs:
            ProcessJobs();

            // Clean up:
            JobQueue.Dispose();
            Timer.Dispose();
            UserInputThread.Abort();

            Console.WriteLine("Done.");
        }



        private void ProcessJobs()
        {
            try
            {
                // Checks if the blocking collection is still up for dequeueing:
                while (!JobQueue.IsCompleted)
                {
                    // The following line blocks the thread until a job is available or throws an exception in case the token is cancelled:
                    JobQueue.Take(JobCancellationToken).Run();
                }
            }
            catch { }
        }



        private void ReadUserInputs()
        {
            // User input thread is running here.
            ConsoleKey key = ConsoleKey.Enter;

            // Reads user inputs and queue them for processing until the escape key is pressed:
            while ((key = Console.ReadKey(true).Key) != ConsoleKey.Escape)
            {
                Job userInputJob = new Job("UserInput", this, new Action<ConsoleKey>(ProcessUserInputs), key);
                JobQueue.Add(userInputJob);
            }
            // Stops processing the JobQueue:
            JobCancellationTokenSource.Cancel();
        }

        private void ProcessUserInputs(ConsoleKey key)
        {
            // Main thread is running here.
            Console.WriteLine($"You just typed '{key}'. (Thread: {Thread.CurrentThread.Name})");
        }



        private void TimerCallback(object param)
        {
            // Timer thread is running here.
            Job job = new Job("TimerJob", this, new Action<string>(ProcessTimer), "A job from timer callback was processed.");
            JobQueue.TryAdd(job); // Just enqueues the job for later processing
        }

        private void ProcessTimer(string message)
        {
            // Main thread is running here.
            Console.WriteLine($"{message} (Thread: {Thread.CurrentThread.Name})");
        }
    }



    /// <summary>
    /// The Job class wraps an object's method call, with or without arguments. This method is called later, during the Job execution.
    /// </summary>
    public class Job
    {
        public string Name { get; }
        private object TargetObject;
        private Delegate TargetMethod;
        private object[] Arguments;

        public Job(string name, object obj, Delegate method, params object[] args)
        {
            Name = name;
            TargetObject = obj;
            TargetMethod = method;
            Arguments = args;
        }

        public void Run()
        {
            try
            {
                TargetMethod.Method.Invoke(TargetObject, Arguments);
            }
            catch(Exception ex)
            {
                Debug.WriteLine($"Unexpected error running job '{Name}': {ex}");
            }
        }

    }
}

关于c# - 在 C# 中实现生产者/消费者模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/733793/

相关文章:

c# - 如何在单元测试中使用环境变量(.net core)

c# - 如何在不安装 .net 的情况下运行 .net 应用程序?

C# 从 Windows 窗体中的 TabPages 中删除填充

c# - 如何声明一个带有连字符的变量

c# - 私有(private)常量与公共(public)只读

javascript - 有没有办法用 Javascript 监听控制键 (ASCII < 10) 事件?

Java - 事件处理设计

javascript - jquery 丢失事件

c# - 在 MVC 中有条件地显示操作链接

C#:判断字符串是否是这种模式;可能的正则表达式