System.Threading.Tasks.Task引起的IIS应用程序池崩溃

本文探讨了IIS应用程序池崩溃的现象及原因,重点分析了由于System.Threading.Tasks.Task未处理异常导致的问题,并提供了详细的解决方案及预防措施。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题现象

IIS应用程序池崩溃(Crash)的特征如下:

1. 从客户端看,浏览器一直处于连接状态,Web服务器无响应。

2. 从服务器端看(Windows Server 2008 + IIS 7.0),在事件日志中会出现Event ID为5010的错误:

A process serving application pool 'q.cnblogs.com' failed to respond to a ping. The process id was '20080'.

这个错误的意思是:IIS检测到程序池'q.cnblogs.com'无响应。为什么没有响应呢?因为程序池'q.cnblogs.com'崩溃了。然后呢?IIS会强制回收应用程序池。

(注:如果在你的Web服务器的事件日志中出现这个错误,一定是某个原因引起了应用程序池崩溃。)

问题原因

我们这次遇到的应用程序池崩溃,是由于在使用System.Threading.Tasks.Task进行异步操作时产生了未处理的异常。

示例代码如下:

Task.Factory.StartNew(() =>
{
//下面的代码未用try..catch捕获异常
//...
});

注:这是一个不需要Callback的异步操作,后续没有task.wait(或者静态方法Task.WaitAll或Task.WaitAny)操作。

当时我们发布程序后,由于Task中代码产生了异常,整个站点无法正常访问,程序池一直处于“崩溃->回收->崩溃->回收”的循环。

解决方法

捕获Task中所有代码的异常,示例代码如下:

Task.Factory.StartNew(() =>
{
try
{
//...
}
catch { }
});

问题分析

在stackoverflow上提到了这个问题的原因

If you create a Task, and you don't ever call task.Wait() or try to retrieve the result of a Task<T>, when the task is collected by the garbage collector, it will tear down your application during finalization. For details, see MSDN's page on Exception Handling in the TPL.

The best option here is to "handle" the exception. 

根据上面的英文,我的理解是:当你创建一个Task,没有调用过task.Wait()或者没有获取它的执行结果,(如果Task中出现了未处理的异常),当这个Task被GC回收时,在GC finalization阶段,会让当前应用程序崩溃。

进一步看MSDN中的Exception Handling (Task Parallel Library)

"Unhandled exceptions that are thrown by user code that is running inside a task are propagated back to the joining thread. ···Exceptions are propagated when you use one of the static or instance Task.Wait or Task(Of TResult).Wait methods···"

翻译:在一个task中运行的代码抛出的未处理异常会被回传给(创建该task的)主线程。···当你调用Task.Wait时,异常才会被回传(给主线程)。

分析:当我们遇到的情况是没调用Task.Wait,也就是异常没有被回传给主线程。下面的这句就提到了这个:

"If you do not wait on a task that propagates an exception, or access its Exception property, the exception is escalated according to the .NET exception policy when the task is garbage-collected."

译:如果你在一个task中没有等待异常被传播,或者访问它的异步特性,在task被GC回收时,该异常会遵循.NET异常策略被逐步升级。

分析:逐步升级的后果就是当前应用程序进程崩溃,对于ASP.NET程序来说,就是应用程序池崩溃。

进一步的解决方法

MSDN上的推荐做法是用Task.ContinueWith观察Task中抛出的异常并进行处理,示例代码如下:

var task1 = Task.Factory.StartNew(() =>
{
throw new MyCustomException("Task1 faulted.");
})
.ContinueWith((t) =>
{
Console.WriteLine("I have observed a {0}",
t.Exception.InnerException.GetType().Name);
},
TaskContinuationOptions.OnlyOnFaulted);

小结

应用程序池崩溃的原因总结 —— System.Threading.Tasks.Task中的代码抛出了未处理的异常,由于没有Task.Wait()操作,异常没有被回传给主线程,在GC回收时,发现这个身份不明的异常。然后,这个异常被一级一级上报,直到当前程序进程的最高领导,最高领导为了顾全大局,果然决定与这个异常同归于尽,也就是让整个应用程序池崩溃。。。

Application: ASD880.exe CoreCLR Version: 9.0.24.52809 .NET Version: 9.0.0 Description: The process was terminated due to an unhandled exception. Exception Info: System.UnauthorizedAccessException: Access to the path is denied. at ASD880.View.SerialPortCommunication.SendDataAsync(Byte[] data) in C:\Users\asd\Desktop\充电头网PD31\ASD880\Page Navigation App\View\Communication.xaml.cs:line 59 at ASD880.View.BatteryMode.executePipeline(JArray jsonArray, Boolean PipeResult, Int32 frequency) in C:\Users\asd\Desktop\充电头网PD31\ASD880\Page Navigation App\View\BatteryMode.xaml.cs:line 1407 at ASD880.View.BatteryMode.ProjectStart_Click(Object sender, RoutedEventArgs e) in C:\Users\asd\Desktop\充电头网PD31\ASD880\Page Navigation App\View\BatteryMode.xaml.cs:line 890 at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__128_0(Object state) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.DispatcherOperation.InvokeImpl() at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) --- End of stack trace from previous location --- at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state) at System.Windows.Threading.DispatcherOperation.Invoke() at System.Windows.Threading.Dispatcher.ProcessQueue() at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs) at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg) at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame) at System.Windows.Application.RunDispatcher(Object ignore) at System.Windows.Application.RunInternal(Window window) at ASD880.App.Main() 分析错误
06-18
using Microsoft.AspNetCore.Mvc; using NAudio.Wave; using System.Diagnostics; using System.IO; using System.Media; using System.Runtime.InteropServices; using System.Threading.Tasks; [ApiController] [Route(“api/tts”)] public class TTSController : ControllerBase { private readonly IWebHostEnvironment _env; public TTSController(IWebHostEnvironment env) { _env = env; } [HttpGet] public async Task<IActionResult> SynthesizeSpeech(string text, string lang = "zh-cn") { // 1. 获取提示音文件路径 var introAudio = Path.Combine(_env.ContentRootPath, "dingdong.mp3"); if (!System.IO.File.Exists(introAudio)) { return StatusCode(500, "提示音文件未找到"); } // 2. 生成临时文件名 var tempDir = Path.Combine(_env.ContentRootPath, "TempAudio"); Directory.CreateDirectory(tempDir); var ttsFile = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp3"); var finalFile = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp3"); try { // 3. 调用Python脚本生成TTS语音 var pythonScript = Path.Combine(_env.ContentRootPath, "tts.py"); var processStartInfo = new ProcessStartInfo { FileName = "python", Arguments = $"\"{pythonScript}\" \"{text}\" \"{ttsFile}\" \"{lang}\"", RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true }; using var ttsProcess = Process.Start(processStartInfo); await ttsProcess.WaitForExitAsync(); if (ttsProcess.ExitCode != 0) { var error = await ttsProcess.StandardError.ReadToEndAsync(); return StatusCode(500, $"TTS失败: {error}"); } // 4. 合并提示音和TTS语音 CombineAudioFiles(introAudio, ttsFile, finalFile); // 5. 在服务端播放合并后的音频 PlayAudioOnServer(finalFile); return Ok($"已播放: {text}"); } finally { // 延迟删除确保播放完成 //await Task.Delay(50000); // 等待5秒确保播放完成 //if (System.IO.File.Exists(ttsFile)) // System.IO.File.Delete(ttsFile); //if (System.IO.File.Exists(finalFile)) // System.IO.File.Delete(finalFile); // 清理临时文件 //if (System.IO.File.Exists(ttsFile)) System.IO.File.Delete(ttsFile); //if (System.IO.File.Exists(finalFile)) System.IO.File.Delete(finalFile); } } private void CombineAudioFiles(string introPath, string ttsPath, string outputPath) { try { // 1. 创建输入文件读取器 using (var introReader = new AudioFileReader(introPath)) using (var ttsReader = new AudioFileReader(ttsPath)) { // 2. 统一输出格式(44.1kHz, 16bit, 立体声) var outputFormat = WaveFormat.CreateIeeeFloatWaveFormat(44100, 2); // 3. 创建输出文件写入器 using (var outputWriter = new WaveFileWriter(outputPath, outputFormat)) { // 4. 创建重采样器(从输入格式转换到输出格式) using (var resampledIntro = new MediaFoundationResampler(introReader, outputFormat)) using (var resampledTTS = new MediaFoundationResampler(ttsReader, outputFormat)) { // 5. 写入提示音(使用Read-Copy循环) CopyWaveProvider(resampledIntro, outputWriter); // 6. 添加1秒静音 AddSilence(outputWriter, TimeSpan.FromSeconds(1)); // 7. 写入TTS语音 CopyWaveProvider(resampledTTS, outputWriter); } } } } catch (Exception ex) { Console.WriteLine($"音频合并失败: {ex.Message}"); throw; } } // 辅助方法:复制音频数据 private void CopyWaveProvider(IWaveProvider source, WaveFileWriter destination) { // 缓冲区大小(10ms数据) int bufferSize = source.WaveFormat.AverageBytesPerSecond / 100; byte[] buffer = new byte[bufferSize]; int bytesRead; while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0) { destination.Write(buffer, 0, bytesRead); } } // 辅助方法:添加静音 private void AddSilence(WaveFileWriter writer, TimeSpan duration) { // 计算静音所需的样本数 int sampleRate = writer.WaveFormat.SampleRate; int channels = writer.WaveFormat.Channels; int samplesNeeded = (int)(sampleRate * duration.TotalSeconds) * channels; // 创建静音缓冲区(IEEE浮点格式,0.0f表示静音) float[] silence = new float[samplesNeeded]; // 写入静音 writer.WriteSamples(silence, 0, silence.Length); } private void PlayAudioOnServer(string audioPath) { //try //{ // // 使用 VLC 命令行播放(安静模式) // var process = new Process(); // process.StartInfo.FileName = @"C:\Program Files\VideoLAN\VLC\vlc.exe"; // process.StartInfo.Arguments = $"--intf dummy --dummy-quiet \"{audioPath}\" vlc://quit"; // process.StartInfo.CreateNoWindow = true; // process.StartInfo.UseShellExecute = false; // process.Start(); // // 不需要等待播放完成 // Console.WriteLine("VLC 已启动播放"); //} //catch (Exception ex) //{ // Console.WriteLine($"VLC播放失败: {ex.Message}"); //} try { Process.Start(new ProcessStartInfo { FileName = "cmd.exe", Arguments = $"/C start /MIN \"\" \"{audioPath}\"", CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden }); Console.WriteLine("已发送播放命令"); } catch (Exception ex) { Console.WriteLine($"播放失败: {ex.Message}"); } //if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) //{ // // Windows播放方式 // Process.Start(new ProcessStartInfo // { // FileName = "cmd.exe", // Arguments = $"/C start \"\" \"{audioPath}\"", // CreateNoWindow = true // }); //} //else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) //{ // // macOS播放方式 // Process.Start("afplay", $"\"{audioPath}\""); //} } } 看下这个接口 实现的是用python的gtts库完成文本转语音并实现播放 我在本地部署iis上后可以正常实现 但是在服务器上部署后 测试没有播放出声音 但是服务器声卡正常 是能放出声音的 是不是服务器上哪个权限设置的不对
最新发布
08-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值