Skip to content

Commit ca4abb6

Browse files
committed
refactor: MaaAgentClient.LinkStart() & Dispose()
1 parent 66e2460 commit ca4abb6

2 files changed

Lines changed: 98 additions & 14 deletions

File tree

src/MaaFramework.Binding.Native/MaaAgentClient.cs

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public static MaaAgentClient Create(MaaResource resource)
7777
protected override void Dispose(bool disposing)
7878
{
7979
base.Dispose(disposing);
80-
_agentServerProcess?.Dispose();
80+
KillAndDisposeAgentServerProcess();
8181
}
8282

8383
/// <inheritdoc/>
@@ -135,23 +135,74 @@ public bool LinkStart()
135135
/// <remarks>
136136
/// Wrapper of <see cref="MaaAgentClientConnect"/>.
137137
/// </remarks>
138-
public bool LinkStart(ProcessStartInfo info)
138+
public bool LinkStart(ProcessStartInfo info, CancellationToken cancellationToken = default)
139139
{
140-
_agentServerProcess = Process.Start(info);
141-
return LinkStart();
140+
if (_agentServerProcess is null or { HasExited: true })
141+
{
142+
_agentServerProcess?.Dispose();
143+
_agentServerProcess = Process.Start(info);
144+
145+
if (_agentServerProcess is null or { HasExited: true })
146+
return false;
147+
}
148+
149+
return LinkStartUnlessProcessExit(_agentServerProcess, cancellationToken).GetAwaiter().GetResult();
142150
}
143151

144152
/// <inheritdoc/>
145153
/// <remarks>
146154
/// Wrapper of <see cref="MaaAgentClientConnect"/>.
147155
/// </remarks>
148-
public bool LinkStart(IMaaAgentClient.AgentServerStartupMethod method)
156+
public bool LinkStart(IMaaAgentClient.AgentServerStartupMethod method, CancellationToken cancellationToken = default)
149157
{
150-
ArgumentException.ThrowIfNullOrEmpty(Id);
151-
ArgumentException.ThrowIfNullOrEmpty(NativeBindingInfo.NativeAssemblyDirectory);
158+
if (_agentServerProcess is null or { HasExited: true })
159+
{
160+
if (string.IsNullOrEmpty(Id) || string.IsNullOrEmpty(NativeBindingInfo.NativeAssemblyDirectory))
161+
{
162+
throw new InvalidOperationException(
163+
$"The {nameof(Id)}({Id ?? "<null>"})" +
164+
$" or {nameof(NativeBindingInfo.NativeAssemblyDirectory)}({NativeBindingInfo.NativeAssemblyDirectory ?? "<null>"})" +
165+
$" is invalid.");
166+
}
152167

153-
_agentServerProcess = method.Invoke(Id, NativeBindingInfo.NativeAssemblyDirectory);
154-
return LinkStart();
168+
_agentServerProcess?.Dispose();
169+
_agentServerProcess = method.Invoke(Id, NativeBindingInfo.NativeAssemblyDirectory);
170+
171+
if (_agentServerProcess is null or { HasExited: true })
172+
return false;
173+
}
174+
175+
return LinkStartUnlessProcessExit(_agentServerProcess, cancellationToken).GetAwaiter().GetResult();
176+
}
177+
178+
/// <inheritdoc/>
179+
public async Task<bool> LinkStartUnlessProcessExit(Process process, CancellationToken cancellationToken)
180+
{
181+
ArgumentNullException.ThrowIfNull(process);
182+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
183+
if (process.HasExited)
184+
return false;
185+
186+
var serverExitTask = process.WaitForExitAsync(cts.Token);
187+
var linkStartTask = Task.Run(LinkStart, cts.Token);
188+
var completedTask = await Task.WhenAny(linkStartTask, serverExitTask).ConfigureAwait(false);
189+
190+
try
191+
{
192+
cts.Token.ThrowIfCancellationRequested();
193+
if (completedTask == serverExitTask)
194+
return false;
195+
196+
return linkStartTask.Result;
197+
}
198+
finally
199+
{
200+
#if NET8_0_OR_GREATER
201+
await cts.CancelAsync();
202+
#else
203+
cts.Cancel();
204+
#endif
205+
}
155206
}
156207

157208
/// <inheritdoc/>
@@ -175,5 +226,20 @@ public bool LinkStop()
175226

176227
/// <inheritdoc/>
177228
public Process AgentServerProcess => _agentServerProcess
178-
?? throw new InvalidOperationException($"The agent server process is unavailable or not managed by {nameof(MaaAgentClient)}.");
229+
?? throw new InvalidOperationException($"The agent server process is unavailable or not managed by {nameof(MaaAgentClient)}.");
230+
231+
private void KillAndDisposeAgentServerProcess()
232+
{
233+
if (_agentServerProcess is null)
234+
return;
235+
236+
if (!_agentServerProcess.HasExited)
237+
{
238+
_agentServerProcess.Kill(entireProcessTree: true);
239+
_agentServerProcess.WaitForExit();
240+
}
241+
242+
_agentServerProcess.Dispose();
243+
_agentServerProcess = null;
244+
}
179245
}

src/MaaFramework.Binding/IMaaAgentClient.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,36 @@ public interface IMaaAgentClient : IMaaDisposable
3434

3535
/// <summary>
3636
/// Starts the agent server process using the specified <see cref="ProcessStartInfo"/> and connects to the agent server.
37+
/// <para>To start a new process, the current <see cref="AgentServerProcess"/> must have exited first.</para>
3738
/// </summary>
3839
/// <param name="info">The process start info.</param>
40+
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation waiting for the connection.</param>
3941
/// <returns><see langword="true"/> if the connection was started successfully; otherwise, <see langword="false"/>.</returns>
40-
bool LinkStart(ProcessStartInfo info);
42+
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> has had cancellation requested.</exception>
43+
bool LinkStart(ProcessStartInfo info, CancellationToken cancellationToken = default);
4144

4245
/// <summary>
4346
/// Starts the agent server process using the specified method and connects to the agent server.
47+
/// <para>To start a new process, the current <see cref="AgentServerProcess"/> must have exited first.</para>
4448
/// </summary>
4549
/// <param name="method">The delegate method that defines how to start the agent server process.</param>
50+
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation waiting for the connection.</param>
4651
/// <returns><see langword="true"/> if the connection was started successfully; otherwise, <see langword="false"/>.</returns>
47-
bool LinkStart(AgentServerStartupMethod method);
52+
/// <exception cref="InvalidOperationException">One or more parameters required by the <paramref name="method"/> are invalid.</exception>
53+
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> has had cancellation requested.</exception>
54+
bool LinkStart(AgentServerStartupMethod method, CancellationToken cancellationToken = default);
55+
56+
/// <summary>
57+
/// Starts the connection asynchronously unless the process has exited.
58+
/// </summary>
59+
/// <param name="process">The process to monitor for exit status.</param>
60+
/// <param name="cancellationToken">An optional token to cancel the asynchronous operation waiting for the connection.</param>
61+
/// <returns>
62+
/// A task that represents the asynchronous operation. The task result contains
63+
/// <see langword="true"/> if the connection was started successfully; otherwise, <see langword="false"/>.
64+
/// </returns>
65+
/// <exception cref="OperationCanceledException">The <paramref name="cancellationToken"/> has had cancellation requested.</exception>
66+
Task<bool> LinkStartUnlessProcessExit(Process process, CancellationToken cancellationToken);
4867

4968
/// <summary>
5069
/// Stops the connection.
@@ -58,8 +77,7 @@ public interface IMaaAgentClient : IMaaDisposable
5877
/// <param name="identifier">The unique identifier used to communicate with the agent server.</param>
5978
/// <param name="nativeAssemblyDirectory">The directory path where the <see cref="MaaFramework"/> native assemblies are located.</param>
6079
/// <returns>
61-
/// A <see cref="Process"/> instance representing the started agent server process,
62-
/// or <see langword="null"/> if the method is used to synchronize with unmanaged processes.
80+
/// A new <see cref="Process"/> that is associated with the process resource, or <see langword="null"/> if no process resource is started.
6381
/// </returns>
6482
delegate Process? AgentServerStartupMethod(string identifier, string nativeAssemblyDirectory);
6583

0 commit comments

Comments
 (0)