You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

133 lines
3.7 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Sln.Wcs.UI.Services;
public interface IEngineProcessService
{
bool IsRunning { get; }
string StatusText { get; }
string? LastError { get; }
event Action<string>? OutputReceived;
event Action? StateChanged;
Task StartAsync();
void Stop();
}
public class EngineProcessService : IEngineProcessService
{
private Process? _process;
private readonly HttpClient _http = new() { Timeout = TimeSpan.FromSeconds(3) };
private const string BaseUrl = "http://localhost:5100";
private readonly StringBuilder _outputBuffer = new();
public bool IsRunning { get; private set; }
public string StatusText => IsRunning ? "运行中" : "已停止";
public string? LastError { get; private set; }
public event Action<string>? OutputReceived;
public event Action? StateChanged;
public async Task StartAsync()
{
if (IsRunning) return;
LastError = null;
_outputBuffer.Clear();
var dllPath = Path.Combine(AppContext.BaseDirectory, "Sln.Wcs.HoistServer.dll");
if (!File.Exists(dllPath))
throw new InvalidOperationException($"找不到 {dllPath}");
_process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"\"{dllPath}\" --urls {BaseUrl}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
},
EnableRaisingEvents = true,
};
_process.OutputDataReceived += (_, e) =>
{
if (e.Data != null) { _outputBuffer.AppendLine(e.Data); OutputReceived?.Invoke(e.Data); }
};
_process.ErrorDataReceived += (_, e) =>
{
if (e.Data != null) { _outputBuffer.AppendLine(e.Data); OutputReceived?.Invoke(e.Data); }
};
_process.Exited += (_, _) =>
{
IsRunning = false;
StateChanged?.Invoke();
_process?.Dispose();
_process = null;
};
_process.Start();
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
// 等待健康检查,最多 20 秒
for (int i = 0; i < 40; i++)
{
await Task.Delay(500);
if (_process.HasExited)
{
LastError = _outputBuffer.ToString();
_process.Dispose();
_process = null;
throw new InvalidOperationException($"进程异常退出:\n{LastError}");
}
if (await HealthCheckAsync())
{
IsRunning = true;
StateChanged?.Invoke();
return;
}
}
LastError = _outputBuffer.ToString();
Stop();
throw new InvalidOperationException($"启动超时 (20s):\n{LastError}");
}
public void Stop()
{
if (_process == null) return;
IsRunning = false;
try
{
if (!_process.HasExited)
{
_process.Kill();
_process.WaitForExit(3000);
}
_process.Dispose();
}
catch { }
finally { _process = null; }
StateChanged?.Invoke();
}
private async Task<bool> HealthCheckAsync()
{
try
{
var res = await _http.GetAsync($"{BaseUrl}/api/health");
return res.IsSuccessStatusCode;
}
catch { return false; }
}
}