Как запустить N задач (или потоков) для запуска того же метода, который может вернуть бесполезные результаты? - PullRequest
0 голосов
/ 14 декабря 2018

У меня есть несколько стохастических функций, которые возвращают Maybe<T>.Когда он дает полезный результат, Maybe содержит результат.

Maybe<T> реализован так:

public readonly struct Maybe<T> {

    public readonly bool ContainsValue;
    public readonly T Value;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public Maybe(bool containsValue, T value) {
        ContainsValue = containsValue;
        Value = value;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static Maybe<T> Just(T value) {
        return new Maybe<T>(
            containsValue: true,
            value: value);
    }

    public static Maybe<T> Empty { get; } = new Maybe<T>(
        containsValue: false,
        value: default
        );

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator Maybe<T>(T value) => Maybe.Just(value);
}

Я бы хотел создать N задач для запуска FuncThatMayFail(),с N = Environment.ProcessorCount.Когда первая задача / поток действительно получает полезный результат, он останавливается и сообщает другим задачам / потокам также остановиться.

Мой текущий подход таков:

public static Maybe<T> RunParallel<T>(int maximumRetries, Func<Maybe<T>> func) {
    if (maximumRetries < 0)
        throw new ArgumentOutOfRangeException(nameof(maximumRetries) + " must be >= 0");
    if (func == null)
        throw new ArgumentNullException(nameof(func));

    var retries = 0;
    var tasks = new Task<Maybe<T>>[Environment.ProcessorCount];
    var finished = 0;

    for (int i = 0; i < tasks.Length; i++) {
        tasks[i] = Task.Run(() => {
            while (true) {
                if (retries >= maximumRetries || finished > 0)
                    return Maybe<T>.Empty;

                var attempt = func();
                if (attempt.ContainsValue) {
                    Interlocked.Increment(ref finished);
                    return attempt;
                } else {
                    Interlocked.Increment(ref retries);
                }
            }
        });
    }

    Task.WaitAny(tasks);

    for (int i = 0; i < tasks.Length; i++) {
        var t = tasks[i];
        if (t.IsCompletedSuccessfully && t.Result.ContainsValue)
            return t.Result;
    }

    return Maybe<T>.Empty;
}

Я разместил это наCodereview просил предложения по улучшению и не получил ни одного.Я чувствую, что этот код некрасив и что, вероятно, есть лучший способ сделать это.Есть ли более элегантный (без использования внешних библиотек) для этого?Я использую C # 7.2 для .Net Core 2.2

1 Ответ

0 голосов
/ 14 декабря 2018

Я обновил код и разместил его ниже.Это не проверено, но ответ там.Вы должны быть в состоянии работать как есть, но если не взять то, что вам нужно из него.

  • Сначала вам нужно добавить CancellationTokenSource и передать Token в Task (s).), чтобы вы могли сигнализировать им, когда нужно остановиться (с точки зрения фреймворков).
  • Затем вам нужно самостоятельно контролировать этот CancellationTokenSource в цикле while, чтобы вручную остановить задачи.
  • Task.WaitAny возвращает индекс Task, который был завершен, поэтому вам не нужно перебирать их, чтобы найти его.
  • Вы также уже возвращаете Maybe<T>.Empty, если Taskзаканчивается без результата, поэтому нет необходимости проверять ContainsValue;просто верните код Result.

ниже и задокументирован, где я внес изменения.

//Make a cancellation token source to signal other tasks to cancel.
CancellationTokenSource cts = new CancellationTokenSource();
for (int i = 0; i < tasks.Length; i++)
{
    tasks[i] = Task.Run(() => {
        while (!cts.IsCancellationRequested) //Monitor for the cancellation token source to signal canceled.
        {
            if (retries >= maximumRetries || finished > 0)
                return Maybe<T>.Empty;

            var attempt = func();
            if (attempt.ContainsValue)
            {
                Interlocked.Increment(ref finished);
                return attempt;
            }
            else
            {
                Interlocked.Increment(ref retries);
            }
        }
        return Maybe<T>.Empty;
    }, cts.Token); //Add the token to the task.
}

var completedTaskIndex = Task.WaitAny(tasks); //Task.WaitAny gives you the index of the Task that did complete.
cts.Cancel(); //Signal the remaining tasks to complete.
var completedTask = tasks[completedTaskIndex]; //Get the task that completed.
return completedTask.Result; //You're returning Maybe<T>.Emtpy from the Task if it fails so no need to check ContainsValue; just return the result.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...