Стирание типов универсальных типов, лучшая альтернатива динамическим - PullRequest
0 голосов
/ 04 октября 2018

Я пытаюсь создать рабочий пул, который может принимать в качестве входных данных любой Func<T> или Action, возвращать задачу и планировать результат этой задачи в каком-то потоке в будущем.

IЯ знаю, что могу использовать ThreadPool или Task.Factory, но я делаю это ради обучения.

Теперь моя реализация ниже полагается на то, что я могу поставить в очередь TaskCompletionSource<T>, обернув ее в dynamic внутри TaskWrapper.Я не чувствую себя комфортно, делая это (поскольку я могу себе представить, что это имеет немалые затраты времени выполнения), но я не знаю никаких альтернатив.

public class WorkerHub
{
    private readonly ConcurrentQueue<TaskWrapper> _tasks;
    private readonly Timer _timer;

    public WorkerHub()
    {
        _timer = new Timer();
        _tasks = new ConcurrentQueue<TaskWrapper>();
    }

    public Task<TResult> Post<TResult>(Func<TResult> func)
    {
        var cts = new TaskCompletionSource<TResult>();
        var wrapper = new TaskWrapper {CompletionSource = cts, Function = func};
        _tasks.Enqueue(wrapper);
        return cts.Task;
    }

    public Task Post(Action action)
    {
        var cts = new TaskCompletionSource<bool>();
        var wrapper = new TaskWrapper {CompletionSource = cts, Function = action, isVoid = true};
        _tasks.Enqueue(wrapper);
        return cts.Task;
    }

    private TaskWrapper Pop()
    {
        _tasks.TryDequeue(out var wrapper);
        return wrapper;
    }


    public void Start()
    {
        _timer.Enabled = true;
        _timer.AutoReset = true;

        _timer.Interval = 2500;
        _timer.Elapsed += (sender, args) =>
        {
            var wrapper = Pop();
            if (wrapper != null) wrapper.CompletionSource.SetResult(wrapper.isVoid ? true : wrapper.Function());
        };
        _timer.Start();
    }

    public void Stop()
    {
    }

    private class TaskWrapper
    {
        public bool isVoid { get; set; }
        public dynamic Function { get; set; }
        public dynamic CompletionSource { get; set; }
    }

Что такое «правильный» способ существованияв состоянии связать с различными типами источников завершения и различными типами функций ввода в одной коллекции?

Ответы [ 2 ]

0 голосов
/ 04 октября 2018

Я думаю, что перемещение некоторой функциональности в TaskWrapper и определение общей реализации, которая вытекает из нее, имеет большой смысл:

private class TaskWrapper
{
    private readonly Action _function;
    private readeonly TaskCompletionSource<bool> _cts;
    public TaskWrapper (Action function, TaskCompletionSource<bool> cts) {
       _function = function;
       _cts = cts;
    }
    protected TaskWrapper () {
       _function = null;
       _cts = null;
    }
    public virtual void DoWork() {
       _function();
       cts.SetResult(true);
    }
}
private class TaskWrapper<T> : TaskWrapper {
    private readonly Func<T> _function;
    private readeonly TaskCompletionSource<T> _cts;
    public TaskWrapper (Func<T> function, TaskCompletionSource<T> cts) : base() {
       _function = function;
       _cts = cts;
    }
    public override void DoWork(){
       _cts.SetResult(_function());
    }
}

И теперь ваш диспетчер может просто вызвать DoWork не зная, использовался ли generic во время строительства.


Я также хотел бы отметить, что ваша текущая реализация должна заставить вас чувствовать себя более чем неудобно.Он не может вызвать Function для Action пунктов.

0 голосов
/ 04 октября 2018

Другой подход, который не предполагает удержания делегата или источника завершения задачи, заключается в использовании лямбда-выражений.

public class WorkerHub {
    private readonly ConcurrentQueue<TaskWrapper> _tasks;
    private readonly Timer _timer;

    public WorkerHub() {
        _timer = new Timer();
        _tasks = new ConcurrentQueue<TaskWrapper>();
    }

    public Task<TResult> Post<TResult>(Func<TResult> func) {
        var cts = new TaskCompletionSource<TResult>();

        Action handler = () => {
            cts.SetResult(func());
        };

        var wrapper = new TaskWrapper { Invoke = handler };
        _tasks.Enqueue(wrapper);
        return cts.Task;
    }

    public Task Post(Action action) {
        var cts = new TaskCompletionSource<bool>();
        Action handler = () => {
            action();
            cts.SetResult(true);
        };
        var wrapper = new TaskWrapper { Invoke = handler };
        _tasks.Enqueue(wrapper);
        return cts.Task;
    }

    private TaskWrapper Pop()
    {
        _tasks.TryDequeue(out var wrapper);
        return wrapper;
    }


    public void Start() {
        _timer.Enabled = true;
        _timer.AutoReset = true;

        _timer.Interval = 2500;
        _timer.Elapsed += (sender, args) => {
            var wrapper = Pop();
            if (wrapper != null)
                wrapper.Invoke();
        };
        _timer.Start();
    }

    public void Stop() {
    }

    private class TaskWrapper {
        public Action Invoke { get; set; }
    }
}

. Создается делегат Action для обработки желаемого поведения, который передается оболочке.должен вызываться при необходимости.

Оболочка теперь становится избыточной и может быть удалена полностью

public class WorkerHub {
    private readonly ConcurrentQueue<Action> _tasks;
    private readonly Timer _timer;

    public WorkerHub() {
        _timer = new Timer();
        _tasks = new ConcurrentQueue<Action>();
    }

    public Task<TResult> Post<TResult>(Func<TResult> func) {
        var cts = new TaskCompletionSource<TResult>();
        Action handler = () => {
            cts.SetResult(func());
        };
        _tasks.Enqueue(handler);
        return cts.Task;
    }

    public Task Post(Action action) {
        var cts = new TaskCompletionSource<bool>();
        Action handler = () => {
            action();
            cts.SetResult(true);
        };
        _tasks.Enqueue(handler);
        return cts.Task;
    }

    public void Start() {
        _timer.Enabled = true;
        _timer.AutoReset = true;

        _timer.Interval = 2500;
        _timer.Elapsed += (sender, args) => {
            Action handler = null;
            if (_tasks.TryDequeue(out  handler) && handler != null)
                handler.Invoke();
        };
        _timer.Start();
    }

    public void Stop() {
    }
}

Да, есть и другие рефакторинг, которые можно сделать, чтобы улучшить этот дизайн, но это должно бытьдостаточно, чтобы донести общую идею до

...