.NET Core Windows Service中的并行任务在几秒钟后挂起。

问题描述 投票:0回答:1

我正试图让一个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服务和多线程非常陌生。

c# multithreading windows-services backgroundworker asp.net-core-3.1
1个回答
0
投票

SemaphoreSlim 可能不是一个将事件转换为事件的适当机制。Task因为它不能传播异常。异常,因为它不能传播异常。TaskCompletionSource 类是一个比较合适的机制。此外,当订阅一个事件时,当我们不想再收到任何通知时,最好取消订阅。取消订阅是通过使用 -= 操作符。

这里有两个类的扩展方法 ScannerClient允許訂閱其特定事件的單一通知,並把該通知以 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.ScanAsyncClient.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 一次一个任务(而不是同时启动所有任务),看看是否有什么不同。

© www.soinside.com 2019 - 2024. All rights reserved.