我正在尝试创建一个工作池,该工作池可以将任何Func<T>
或Action
用作输入,返回一个Task并在以后的某个时间安排该任务的结果。
我知道我可以使用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; }
}
能够绑定到同一集合中不同类型的完成源和不同类型的输入函数的“正确”方法是什么?
答案 0 :(得分:4)
另一种不涉及委托或任务完成源的方法是通过lambda表达式
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() {
}
}
是的,可以进行更多的重构来改进此设计,但这应该足以使总体思想得以理解
答案 1 :(得分:2)
我认为将某些功能移入 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
,而不必知道泛型是否是构建期间使用的泛型。
我还要指出,您当前的实施方式应该会让您感到不舒服。无法为Function
个项目调用 Action
。