c#多进程开发

介绍

相比多线程开发,C#的多进程开发相对麻烦,无法像多线程一样对某个函数直接启动。只能将函数打包成exe文件,再使用process启动exe文件并传参,传入的参数必须序列化为字符串。

过程

1、构建执行函数

因为要将数据分进程处理,所以构建了一个函数,输入参数对象 bundleMerge,执行函数即可运行进程代码。

public static  void createBundle(bundleMerge bm){
    //业务代码
}

2、封装序列化类

输入的参数只能是字符串,因此将参数对象序列化,在序列化过程中使用各种第三方包都报错,最后找到一个序列化帮助类

/// <summary>
    /// 序列化帮助类
    /// </summary>
    public class SerializeHelper
    {
        /// <summary>
        /// 序列obj对象为base64字串
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string Serialize(object obj)
        {
            if (obj == null)
                return string.Empty;

            try
            {
                var formatter = new BinaryFormatter();
                var stream = new MemoryStream();
                formatter.Serialize(stream, obj);
                stream.Position = 0;
                byte[] buffer = new byte[stream.Length];
                stream.Read(buffer, 0, buffer.Length);
                stream.Close();
                return Convert.ToBase64String(buffer);
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("序列化{0}失败,原因:{1}", obj, ex.Message));
            }
        }

        /// <summary>
        /// 反序列化字符串到对象
        /// </summary>
        /// <param name="str">要转换为对象的字符串</param>
        /// <returns>反序列化出来的对象</returns>
        public static T Deserialize<T>(string str)
        {
            var obj = default(T);
            if (string.IsNullOrEmpty(str))
                return obj;

            try
            {
                var formatter = new BinaryFormatter();
                byte[] buffer = Convert.FromBase64String(str);
                MemoryStream stream = new MemoryStream(buffer);
                obj = (T)formatter.Deserialize(stream);
                stream.Close();
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("序列化{0}失败,原因:{1}", obj, ex.Message));
            }
            return obj;
        }
    }

3、构建exe程序

新建一个项目,项目类型修改为应用程序
image.png
在项目main函数中使用业务代码和反序列化后的参数作为传入对象:

    public class tiles2bundleEXE
    {

        static void Main(string[] args) {
            // 获取传递的JSON字符串
            string json = args[0];
            // 反序列化JSON字符串为对象
            Console.WriteLine(json);
            bundleMerge bm = SerializeHelper.Deserialize<bundleMerge>(json);
            //bundleMerge bm = JsonConvert.DeserializeObject<bundleMerge>(json);
            // 业务代码
            tiles2bundle.createBundle(bm);
            //执行完成后结束进程
            Environment.Exit(0);

        }
    }

生成解决方案后,得到exe文件
image.png

4、多进程帮助类

copy了一个多进程帮助类,用来控制进程数量和创建进程

public class ProcessHelper
    {
        public delegate void countHandler(int i);
        public event countHandler count;

        public  bool processRun(string processPath, List<string> listBundle)
        {
            int maxProcessCount = Environment.ProcessorCount;
            List<Task> taskItems = new List<Task>();
            Queue<string> collectPathItems = new Queue<string>();
            foreach (var item in listBundle)
            {
                collectPathItems.Enqueue(item);
            }
            string exeName = Path.GetFileName(processPath);
            Process[] processItems;
            int cursor = 0;
            while (!(collectPathItems.Count == 0 && taskItems.Count == 0))
            {
                foreach (Task taskItem in new List<Task>(taskItems))
                {
                    if (taskItem.Status == TaskStatus.Canceled || taskItem.Status == TaskStatus.Faulted || taskItem.Status == TaskStatus.RanToCompletion)
                    {
                        taskItems.Remove(taskItem);
                    }
                }
                // 如果collectPathItems.Count == 0,则不会有新的任务被添加进来,因此不需要执行下边其他代码。
                // 而只需要等待上边的任务完成跳出循环即可。
                if (collectPathItems.Count == 0)
                {
                    Thread.Sleep(5 * 1000);
                    continue;
                }
                
                processItems = Process.GetProcessesByName(exeName);
                if (processItems.Length >= maxProcessCount)
                {
                    Thread.Sleep(5 * 1000);
                    continue;
                }
                int dequeueCount = ((maxProcessCount - processItems.Length) > collectPathItems.Count) ? collectPathItems.Count : (maxProcessCount - processItems.Length);
                for (int i = 0; i < dequeueCount; i++)
                {
                    taskItems.Add(Task.Factory.StartNew(
                        (object mystate) =>
                        {
                            Process process = Process.Start(processPath, mystate.ToString());
                            process.WaitForExit();
                            count?.Invoke(0);
                        }
                       , collectPathItems.Dequeue())
                       );
                }
                // sleep 30 seconds...
                Thread.Sleep(5 * 1000);
                cursor++;

            }
            //循环等待进程运行完成
            processItems = Process.GetProcessesByName(exeName);
            while (processItems.Length  != 0)
            {
                Thread.Sleep(10 * 1000);
            }
            return true;
        }
    }

5、在form中使用多进程帮助类和exe对象

//新建一个列表,用来接收序列化后的参数对象
List<string> listPara = new List<string>();
foreach (var item in listBundle)
{
    listPara.Add(SerializeHelper.Serialize(item)   );
}
//将路径指向生成后的exe文件
string exePath = Environment.CurrentDirectory + "\\tiles2bundle.exe";
ProcessHelper ph = new ProcessHelper();
ph.count += new ProcessHelper.countHandler(ProcessChanged);
//使用帮助类运行
ph.processRun(exePath, listPara);

总结

在c#中构建一个多进程程序比较麻烦,不如python中直接pool.starmap启动进程池方便。

posted @ 2023-09-08 19:30  粥和  阅读(812)  评论(0编辑  收藏  举报