Skip to content

Instantly share code, notes, and snippets.

@BYJRK
Last active June 27, 2024 01:39
Show Gist options
  • Save BYJRK/29f10593cb6899294deb08dd972a6a27 to your computer and use it in GitHub Desktop.
Save BYJRK/29f10593cb6899294deb08dd972a6a27 to your computer and use it in GitHub Desktop.
Wrap a long-running synchronous method into an asynchronous method and provide cancellation functionality.
using System.Diagnostics;
public class CancelableProcessTask
{
private readonly string _filename;
private readonly string _arguments;
private Process? _process;
private TaskCompletionSource? _tcs;
private int _isRunning = 0;
public CancelableProcessTask(string filename, string arguments)
{
_filename = filename;
_arguments = arguments;
}
public Task RunAsync(CancellationToken token)
{
if (Interlocked.CompareExchange(ref _isRunning, 1, 0) == 1)
throw new InvalidOperationException("Task is already running");
_tcs = new TaskCompletionSource();
_process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = _filename,
Arguments = _arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
_process.Start();
_process.EnableRaisingEvents = true;
_process.Exited += (sender, args) =>
{
if (_process.ExitCode == 0)
_tcs.SetResult();
else
{
if (token.IsCancellationRequested)
_tcs.SetCanceled(token);
else
_tcs.SetException(new Exception($"Process exited with code {_process.ExitCode}"));
}
};
token.Register(() =>
{
if (Interlocked.CompareExchange(ref _isRunning, 0, 1) == 1)
{
_process.Kill();
}
});
return _tcs.Task;
}
}
public class CancelableThreadTask
{
private Thread? _thread;
private readonly Action _action;
private readonly Action<Exception>? _onError;
private readonly Action? _onCompleted;
private TaskCompletionSource? _tcs;
private int _isRunning = 0;
public CancelableThreadTask(Action action, Action<Exception>? onError = null, Action? onCompleted = null)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
_onError = onError;
_onCompleted = onCompleted;
}
public Task RunAsync(CancellationToken token)
{
if (Interlocked.CompareExchange(ref _isRunning, 1, 0) == 1)
throw new InvalidOperationException("Task is already running");
_tcs = new TaskCompletionSource();
_thread = new Thread(() =>
{
try
{
_action();
_tcs.SetResult();
_onCompleted?.Invoke();
}
catch (Exception ex)
{
if (ex is ThreadInterruptedException)
_tcs.TrySetCanceled(token);
else
_tcs.TrySetException(ex);
_onError?.Invoke(ex);
}
finally
{
Interlocked.Exchange(ref _isRunning, 0);
}
});
token.Register(() =>
{
if (Interlocked.CompareExchange(ref _isRunning, 0, 1) == 1)
{
_thread.Interrupt();
_thread.Join();
_tcs.TrySetCanceled(token);
}
});
_thread.Start();
return _tcs.Task;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment