
Ø 前言
本文主要记录子线程导致 Topshelf 和 Quartz.NET 的 Windows 服务停止的现象,以及使用几种常用子线程的注意事项。因为我们有时可能需要开启多个线程执行复杂的逻辑,如果某个子线发生了异常就导致服务停止了,那还怎么愉快的玩耍?!
1. 还是以之前使用 Quartz.NET 实现作业串行执行为例,我们模拟在“发送短信”和“发送邮件”中发生异常的情况,代码如下:
1) 首先修改 SendSMSJob 作业
/// <summary>
/// 发送短信作业。
/// </summary>
public class SendSMSJob : IJob
{
/// <summary>
/// 作业被触发时执行该方法。
/// </summary>
public void Execute(IJobExecutionContext context)
{
Log.Logger.InfoFormat("开始执行发送短信作业,线程Id为:{0}", Thread.CurrentThread.ManagedThreadId);
int i = 10, j = 0;
int r = i / j;
Log.Logger.InfoFormat("计算结果:{0} / {1} = {2}", i, j, r);
Log.Logger.Info("发送短信作业执行结束");
}
}
2) 然后再修改 SendMailJob 作业
/// <summary>
/// 发送邮件作业。
/// </summary>
public class SendMailJob : IJob
{
/// <summary>
/// 异步方法。
/// </summary>
public async void AsyncMethod1()
{
await Task.Run(() =>
{
try
{
Log.Logger.InfoFormat("开始执行(异步方法),线程为:{0}", Thread.CurrentThread.ManagedThreadId); ;
string str = ((object)null).GetType().Name;
Log.Logger.InfoFormat("结束执行(异步方法),结果:{0}", str);
}
catch (Exception ex)
{
Log.Logger.ErrorFormat("发送邮件作业发生异常:{0}", ex.Message);
}
});
}
/// <summary>
/// 作业被触发时执行该方法。
/// </summary>
public void Execute(IJobExecutionContext context)
{
Log.Logger.InfoFormat("开始执行发送邮件作业,线程Id为:{0}", Thread.CurrentThread.ManagedThreadId);
//1. 使用委托异步调用的线程
Action action = new Action(() =>
{
try
{
Log.Logger.InfoFormat("开始执行(委托异步),线程为:{0}", Thread.CurrentThread.ManagedThreadId); ;
string str = ((object)null).GetType().Name;
Log.Logger.InfoFormat("结束执行(委托异步),结果:{0}", str);
}
catch (Exception ex)
{
Log.Logger.ErrorFormat("发送邮件作业发生异常:{0}", ex.Message);
}
});
action.BeginInvoke((asyncResult) =>
{
Action aciton = asyncResult.AsyncState as Action;
aciton.EndInvoke(asyncResult);
}, action);
//2. 使用开启的新线程
Thread thread = new Thread(() =>
{
try
{
Log.Logger.InfoFormat("开始执行(开启新线程),线程为:{0}", Thread.CurrentThread.ManagedThreadId); ;
string str = ((object)null).GetType().Name;
Log.Logger.InfoFormat("结束执行(开启新线程),结果:{0}", str);
}
catch (Exception ex)
{
Log.Logger.ErrorFormat("发送邮件作业发生异常:{0}", ex.Message);
}
});
thread.Start();
//3. 使用线程池中的线程
System.Threading.ThreadPool.QueueUserWorkItem((state) =>
{
try
{
Log.Logger.InfoFormat("开始执行(线程池),线程为:{0}", Thread.CurrentThread.ManagedThreadId); ;
string str = ((object)null).GetType().Name;
Log.Logger.InfoFormat("结束执行(线程池),结果:{0}", str);
}
catch (Exception ex)
{
Log.Logger.ErrorFormat("发送邮件作业发生异常:{0}", ex.Message);
}
});
//4. 使用异步方法中的线程(以 System.Threading.Tasks.Task.Run() 方式)
AsyncMethod1();
//5. 使用异步任务中的线程(以 System.Threading.Tasks.Task.Run() 方式)
System.Threading.Tasks.Task.Run(() =>
{
Log.Logger.InfoFormat("开始执行(Task.Run()),线程为:{0}", Thread.CurrentThread.ManagedThreadId); ;
string str = ((object)null).GetType().Name;
Log.Logger.InfoFormat("结束执行(Task.Run()),结果:{0}", str);
});
//6. 使用异步任务中的线程(以 System.Threading.Tasks.Task.Factory.StartNew() 方式)
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
Log.Logger.InfoFormat("开始执行(Task.Factory.StartNew()),线程为:{0}", Thread.CurrentThread.ManagedThreadId); ;
string str = ((object)null).GetType().Name;
Log.Logger.InfoFormat("结束执行(Task.Factory.StartNew()),结果:{0}", str);
}).Start();
Log.Logger.Info("发送邮件作业执行结束");
}
}
2. 分别运行 SendMailJob 作业的几种方式(运行时注释其他 5 种方式)
1) 没有加 try/catch 的情况
2) 加了 try/catch 的情况
3) 分析与总结
1. 首先主线程(就是 windows 服务主动开启的线程)发生异常时,终止当前线程执行,但不会停止服务。
2. 【推荐】异步任务(Task)开启的线程发生异常时,也是终止当前线程执行,但不会停止服务。
3. 使用(未加 try/catch 时)委托异步调用的线程、开启的新线程、线程池中的线程、异步方法中的线程,发生异常时,会停止服务!
4. 使用(加 try/catch 时)开启的新线程、线程池中的线程、异步方法中的线程,发生异常时,不会停止服务。
5. 使用(加 try/catch 时)委托异步调用的线程同样会停止服务!