[英]C# process IAsyncEnumerable items with several parallel tasks in asp.net core hosted service consumer
[英]Parallel tasks in .NET Core Windows Service hang after a few seconds
我正在尝试运行 Windows 服务。 该服务应使用工作人员 object 来生成多个任务。
我在工作人员 object 和每个任务中都使用SemaphoreSlim
来等待事件完成,如下所示:
public static IHostBuilder ConfigureServices(this IHostBuilder builder)
{
builder.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<WorkerService>();
services.AddSingleton<WorkerClient>();
});
return builder;
}
工人服务
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);
}
}
工人客户端
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();
}
}
任务运行器
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 中启动它时,整个构造运行良好。 但是当我使用sc
实用程序创建服务并启动它时,它在 1-2 次运行后挂起。
我不知道我做错了什么,因为我对 Windows 服务和多线程非常陌生。
SemaphoreSlim
可能不是将事件转换为Task
的适当机制,因为它不能传播异常。 TaskCompletionSource
class 是更适合此目的的机制。 此外,在订阅事件时,如果我们不想收到任何进一步的通知,最好取消订阅。 取消订阅是使用-=
运算符实现的。
这是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
任务(而不是同时启动所有这些任务),看看是否有什么不同。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.