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程序
新建一个项目,项目类型修改为应用程序
在项目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文件
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启动进程池方便。