如何在MSBuild任务异步线程中记录日志并实现进程完成通知
问题描述
我有一个启动进程后直接返回的MSBuild目标,希望在该进程完成时显示通知。
首次尝试用Log.LogMessage记录任务输出,但没有任何内容,推测进程对象在读取输出前就被释放了(但进程本身能正常运行到完成):
<UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <!--The file path is the full path to the executable file to run--> <FilePath ParameterType="System.String" Required="true" /> <!--The arguments should contain all the command line arguments that need to be sent to the application--> <Arguments ParameterType="System.String" Required="true" /> <WorkingDirectory ParameterType="System.String" Required="true"/> </ParameterGroup> <Task> <Code Type="Fragment" Language="cs"> System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo(FilePath, Arguments); processStartInfo.WorkingDirectory = WorkingDirectory; processStartInfo.UseShellExecute = false; processStartInfo.RedirectStandardOutput = true; processStartInfo.CreateNoWindow = true; var p = new System.Diagnostics.Process(); p.StartInfo = processStartInfo; p.OutputDataReceived += (sender, args) => { if (!string.IsNullOrEmpty(args.Data)) { Log.LogMessage(MessageImportance.High, args.Data); } }; p.Start(); p.BeginOutputReadLine(); </Code> </Task> </UsingTask>
第二次尝试把代码放进System.Threading.Tasks.Task里,等待进程完成后调用Log.LogMessage,还是没有输出:
<UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <!--The file path is the full path to the executable file to run--> <FilePath ParameterType="System.String" Required="true" /> <!--The arguments should contain all the command line arguments that need to be sent to the application--> <Arguments ParameterType="System.String" Required="true" /> <WorkingDirectory ParameterType="System.String" Required="true"/> </ParameterGroup> <Task> <Code Type="Fragment" Language="cs"> System.Threading.Tasks.Task.Run(() => { System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo(FilePath, Arguments); processStartInfo.WorkingDirectory = WorkingDirectory; processStartInfo.UseShellExecute = false; processStartInfo.CreateNoWindow = true; var p = new System.Diagnostics.Process(); p.StartInfo = processStartInfo; p.Start(); p.WaitForExit(); Log.LogMessage(MessageImportance.High, "Finished"); }); </Code> </Task> </UsingTask>
请问有没有可行的方法实现进程完成时的通知功能?
解决方案
核心问题分析
两次尝试失败的原因都是MSBuild任务执行完毕后,任务上下文被销毁,后续异步回调/子线程无法再通过Log对象输出内容:
- 第一次尝试:启动进程并开启异步输出读取后,任务直接返回,进程对象被GC回收,异步回调无法触发;
- 第二次尝试:用
Task.Run启动子线程后任务立即返回,MSBuild任务上下文已结束,子线程里的Log.LogMessage无法输出到MSBuild控制台。
可行实现方式
要实现异步进程完成通知,需要让MSBuild任务持续存活直到进程结束,同时正确处理异步输出和进程退出事件。以下是两种可靠方案:
方案1:同步等待进程结束(简单直接)
如果不需要任务立即返回,直接在任务中同步等待进程完成,之后输出通知:
<UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <FilePath ParameterType="System.String" Required="true" /> <Arguments ParameterType="System.String" Required="true" /> <WorkingDirectory ParameterType="System.String" Required="true"/> </ParameterGroup> <Task> <Code Type="Fragment" Language="cs"> var processStartInfo = new System.Diagnostics.ProcessStartInfo(FilePath, Arguments) { WorkingDirectory = WorkingDirectory, UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true }; using (var p = new System.Diagnostics.Process()) { p.StartInfo = processStartInfo; p.OutputDataReceived += (sender, args) => { if (!string.IsNullOrEmpty(args.Data)) { Log.LogMessage(MessageImportance.High, args.Data); } }; p.Start(); p.BeginOutputReadLine(); p.WaitForExit(); // 同步等待进程结束,任务不会立即返回 Log.LogMessage(MessageImportance.High, "进程已完成!"); } </Code> </Task> </UsingTask>
方案2:异步等待(保持任务存活,不阻塞其他任务)
如果必须让任务立即返回但仍需进程完成通知,可以借助MSBuild的异步任务机制,实现ITaskAsync接口(需使用MSBuild 16.0+版本):
<UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> <ParameterGroup> <FilePath ParameterType="System.String" Required="true" /> <Arguments ParameterType="System.String" Required="true" /> <WorkingDirectory ParameterType="System.String" Required="true"/> </ParameterGroup> <Task> <Code Type="Class" Language="cs"> using System; using System.Diagnostics; using System.Threading.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; public class ExecAsync : Task, ITaskAsync { [Required] public string FilePath { get; set; } [Required] public string Arguments { get; set; } [Required] public string WorkingDirectory { get; set; } public async Task ExecuteAsync() { var processStartInfo = new ProcessStartInfo(FilePath, Arguments) { WorkingDirectory = WorkingDirectory, UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true }; using (var p = new Process()) { p.StartInfo = processStartInfo; p.OutputDataReceived += (sender, args) => { if (!string.IsNullOrEmpty(args.Data)) { Log.LogMessage(MessageImportance.High, args.Data); } }; p.Start(); p.BeginOutputReadLine(); await Task.Run(() => p.WaitForExit()); // 异步等待进程结束 Log.LogMessage(MessageImportance.High, "进程已完成!"); } } } </Code> </Task> </UsingTask>
关键注意事项
- 方案1会阻塞MSBuild直到进程结束,适合不需要并行执行其他任务的场景;
- 方案2使用MSBuild官方异步任务接口,不会阻塞MSBuild主线程,需确保使用MSBuild 16.0及以上版本;
- 必须保留进程对象的引用(比如用
using包裹),避免被GC回收导致异步回调失效; - 若需要捕获错误输出,还需添加
RedirectStandardError = true和ErrorDataReceived事件处理。
内容的提问来源于stack exchange,提问作者Florian
相关产品推荐
相关产品推荐

