我正试图让一个Windows服务运行。该服务应该使用一个worker对象来生成多个任务。
我使用了 SemaphoreSlim
中的Worker对象和每个任务都要等待事件完成,就像这样。
public static IHostBuilder ConfigureServices(this IHostBuilder builder)
{
builder.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<WorkerService>();
services.AddSingleton<WorkerClient>();
});
return builder;
}
WorkerService
public WorkerService(ILogger<WorkerService> logger, WorkerClient workerClient)
{
_logger = logger;
_workerClient = workerClient;
_bleClient.OnValuesReceived += _bleClient_OnValuesReceived;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
await _workerClient.Run();
}
catch(Exception ex)
{
_logger.LogCritical(ex, "Error while running worker client.");
}
await Task.Delay(TimeSpan.FromSeconds(_scanDelay), stoppingToken);
}
}
WorkerClient
public class WorkerClient
{
private Scanner _scanner;
private SemaphoreSlim _lock;
public WorkerClient()
{
_lock = new SemaphoreSlim(0, 1);
_scanner = new Scanner();
_scanner.OnScanFinished += scanner_ScanFinished;
}
public async Task Run()
{
_scanner.Scan();
await _lock.WaitAsync();
}
private void scanner_ScanFinished(object sender, string[] macs)
{
var tasks = new List<Task>();
foreach(var mac in macs)
{
var client = new TaskRunner(mac);
tasks.Add(client.Run());
}
if(tasks.Count > 0)
{
try
{
var task = Task.WhenAll(tasks.ToArray());
await task;
}
catch(Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
_lock.Release();
}
}
TaskRunner
public class TaskRunner
{
private SemaphoreSlim _lock;
private Client _client;
public TaskRunner(string mac)
{
_lock = new SemaphoreSlim(0, 1);
_client = new Client(mac);
_client.OnWorkFinished += client_WorkFinished;
}
public async Task Run()
{
_client.DoWork();
await _lock.WaitAsync();
}
private void client_WorkFinished(object sender, EventArgs args)
{
_lock.Release();
}
}
当我在控制台或VS内部启动它时,整个结构运行正常。但是当我使用以下方法创建一个服务时,在运行1-2次后就挂掉了 sc
实用程序并启动它。
我不知道自己做错了什么,因为我对Windows服务和多线程非常陌生。
该 SemaphoreSlim
可能不是一个将事件转换为事件的适当机制。Task
因为它不能传播异常。异常,因为它不能传播异常。TaskCompletionSource
类是一个比较合适的机制。此外,当订阅一个事件时,当我们不想再收到任何通知时,最好取消订阅。取消订阅是通过使用 -=
操作符。
这里有两个类的扩展方法 Scanner
和 Client
允許訂閱其特定事件的單一通知,並把該通知以 Task
.
public static class ScannerExtensions
{
public static Task<string[]> ScanAsync(this Scanner source)
{
var tcs = new TaskCompletionSource<string[]>();
Action<object, string[]> evenHandler = null;
evenHandler = (s, macs) =>
{
source.OnScanFinished -= evenHandler;
tcs.TrySetResult(macs);
};
source.OnScanFinished += evenHandler;
try
{
source.Scan();
}
catch (Exception ex)
{
source.OnScanFinished -= evenHandler;
tcs.SetException(ex);
}
return tcs.Task;
}
}
public static class ClientExtensions
{
public static Task DoWorkAsync(this Client source)
{
var tcs = new TaskCompletionSource<object>();
EventHandler evenHandler = null;
evenHandler = (s, e) =>
{
source.OnWorkFinished -= evenHandler;
tcs.TrySetResult(null);
};
source.OnWorkFinished += evenHandler;
try
{
source.DoWork();
}
catch (Exception ex)
{
source.OnWorkFinished -= evenHandler;
tcs.SetException(ex);
}
return tcs.Task;
}
}
你可以使用扩展方法 Scanner.ScanAsync
和 Client.DoWorkAsync
来重构 ExecuteAsync
你的服务方法是这样的。
private Scanner _scanner = new Scanner();
protected override async Task ExecuteAsync(CancellationToken token)
{
while (true)
{
Task delayTask = Task.Delay(TimeSpan.FromSeconds(_scanDelay), token);
try
{
string[] macs = await _scanner.ScanAsync();
Task[] doWorktasks = macs.Select(mac =>
{
var client = new Client(mac);
return client.DoWorkAsync();
}).ToArray();
await Task.WhenAll(doWorktasks);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
await delayTask;
}
}
不知道这是否能解决你的问题,但我认为这是一个正确的改变。
如果问题仍然存在,你可以尝试创建和等待的 client.DoWorkAsync
一次一个任务(而不是同时启动所有任务),看看是否有什么不同。