Параллельные задачи в. NET Core Windows Сервис зависает через несколько секунд - PullRequest
0 голосов
/ 25 мая 2020

Я пытаюсь запустить службу Windows. Служба должна использовать рабочий объект для создания нескольких задач.

Я использую SemaphoreSlim как в рабочем объекте, так и в каждой задаче, чтобы дождаться завершения событий sh, например:

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 Services и многопоточности.

1 Ответ

0 голосов
/ 25 мая 2020

SemaphoreSlim, вероятно, не является подходящим механизмом для преобразования события в Task, потому что он не может распространять исключения. Класс TaskCompletionSource является более подходящим механизмом для этой цели. Также при подписке на событие рекомендуется отказаться от подписки, если мы не хотим получать дальнейшие уведомления. Отказ от подписки осуществляется с помощью оператора -=.

Вот два метода расширения для классов Scanner и Client, которые позволяют подписаться на их специфические события c для одно уведомление и распространить это уведомление как 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 задачи по одной (вместо одновременного запуска всех), чтобы посмотреть, имеет ли это значение.

...