题
我不断发现自己必须编写定时 Windows 服务来轮询外部队列或运行定时进程。因此,我有一个相当强大的模板来执行此操作,但我发现每次编写服务来执行此操作时,我都会从相同的模板开始,然后在其中编写流程。
今天早上,我发现自己想知道是否真的可以将这个模板变成一个可以将我的流程注入其中的框架。我的基本模板只有 122 行代码。由于每个过程的要求不同 - 即不同数量的参数、不同的参数类型和不同的依赖项(有些依赖于 Web 服务,有些依赖于数据库等)。我不知道如何设置我的基本模板来接收注入的进程。
模板的核心只是一个计时器,它在初始化和启动进程时停止,然后在进程完成后重新启动计时器。然后,我将流程方法和所有依赖项添加到模板中。
有谁知道如何做到这一点?我研究过依赖注入,并且经常使用它来注入数据存储连接之类的东西?有没有办法将具有未知数量/类型参数的委托注入到类中?我看这个的方式不对吗?
这是我有的模板:
TimedProcess.Template.cs
using System;
using System.Timers;
public partial class TimedProcess : IDisposable
{
private Timer timer;
public bool InProcess { get; protected set; }
public bool Running
{
get
{
if (timer == null)
return false;
return timer.Enabled;
}
}
private void InitTimer(int interval)
{
if (timer == null)
{
timer = new Timer();
timer.Elapsed += TimerElapsed;
}
Interval = interval;
}
public void InitExecuteProcess()
{
timer.Stop();
InProcess = true;
RunProcess();
InProcess = false;
timer.Start();
}
public void TimerElapsed(object sender, ElapsedEventArgs e)
{
InitExecuteProcess();
}
public void Start()
{
if (timer != null && timer.Interval > 0)
timer.Start();
}
public void Start(int interval)
{
InitTimer(interval);
Start();
}
public void Stop()
{
timer.Stop();
}
public TimedProcess()
: this(0)
{
}
public TimedProcess(int interval)
{
if (interval > 0)
InitTimer(interval);
}
private disposed = false;
public Dispose(bool disposing)
{
if (disposed || !disposing)
return;
timer.Dispose();
disposed = true;
}
public Dispose()
{
Dispose(true);
}
~TimedProcess()
{
Dispose(false);
}
}
定时进程.cs
using System;
public partial class TimedProcess
{
public void RunProcess()
{
//Hook process to run in here
}
}
因此,我希望对其进行修改,以便我的 Windows 服务生成一个新的 TimedProcess 并将其注入需要运行的进程,从而从我的 Windows 服务中完全删除 TimedProcess 代码并使其引用 DLL。
编辑: :感谢大家的帮助。我意识到,如果我将 RunProcess() 方法推到 TimedProcess 库之外并通过 那 作为构造函数中的一个 Action,那么这就按照我想要的方式简化了一切:
[为了简洁起见,进行了简化]
public class TimedProcess
{
Action RunProcess;
Timer timer = new Timer();
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
if (RunProcess != null)
RunProcess();
}
public TimedProcess(Action action, int interval)
{
timer.Interval = interval;
RunProcess = action;
timer.Start();
}
}
解决方案
这里的一种方法是使用捕获的变量,以便所有代理基本上变为Action
或者Func<T>
- 并通过匿名方法的魔力将其余代码留给调用者,捕获的变量等等 - 即
DoStuff( () => DoSomethingInteresting("abc", 123) );
(警告:注意异步/捕获 - 通常是一个糟糕的组合)
其中DoStuff
接受Expression
。然后当你调用<=>时,参数会自动添加等等。在某些RPC库代码中,我采用了这种方法,使用<=> - 所以我表达了一个服务接口(正常),然后有方法像:
Invoke(Expression<Action<TService>> action) {...}
Invoke<TValue>(Expression<Func<TService,TValue>> func) {...}
叫,例如:
proxy.Invoke(svc => svc.CancelOrder(orderNumber));
然后调用者说如果我们有一个接口的实现 - 除了我们从未真正做过;相反,我们将<=>分开并查看被调用的方法,args等 - 并将它们传递给RPC层。如果您有兴趣,可以参考更多这里或代码有此处
其他提示
使用一组未知参数的委托的一种方法是传递一个对象数组。然后,您可以使用数组的长度作为参数的数量,并且由于任何类型都可以转换为对象,因此您可以传递任何内容。
您可以使用不带参数的委托来表示<!> quot; service call <!> quot;:
ThreadStart taskToPerform = delegate() { // do your stuff here
YourService.Call(X, Y, Z);
};
Template.AddProcess(taskToPerform);
为了完整起见,这里有一些示例实现。
两者都使用已经讨论过的包装委托方法。一种使用“params”,一种使用泛型。两者都避免了“异步/捕获”问题。事实上,这与事件的实现方式非常相似。
提前抱歉,这一切都在一个长代码块中。我将其分为三个子命名空间:
- FakeDomain(例如持有模拟)
- usingParams(保存使用 params 关键字的实现)
- usingGenerics(保存使用泛型的实现)
见下文:
using System;
using System.Timers;
using StackOverflow.Answers.InjectTaskWithVaryingParameters.FakeDomain;
namespace StackOverflow.Answers.InjectTaskWithVaryingParameters
{
public static class ExampleUsage
{
public static void Example1()
{
// using timed task runner with no parameters
var timedProcess = new UsingParams.TimedProcess(300, FakeWork.NoParameters);
var timedProcess2 = new UsingGenerics.TimedProcess(300, FakeWork.NoParameters);
}
public static void Example2()
{
// using timed task runner with a single typed parameter
var timedProcess =
new UsingParams.TimedProcess(300,
p => FakeWork.SingleParameter((string)p[0]),
"test"
);
var timedProcess2 =
new UsingGenerics.TimedProcess<StringParameter>(
300,
p => FakeWork.SingleParameter(p.Name),
new StringParameter()
{
Name = "test"
}
);
}
public static void Example3()
{
// using timed task runner with a bunch of variously typed parameters
var timedProcess =
new UsingParams.TimedProcess(300,
p => FakeWork.LotsOfParameters(
(string)p[0],
(DateTime)p[1],
(int)p[2]),
"test",
DateTime.Now,
123
);
var timedProcess2 =
new UsingGenerics.TimedProcess<LotsOfParameters>(
300,
p => FakeWork.LotsOfParameters(
p.Name,
p.Date,
p.Count),
new LotsOfParameters()
{
Name = "test",
Date = DateTime.Now,
Count = 123
}
);
}
}
/*
* Some mock objects for example.
*
*/
namespace FakeDomain
{
public static class FakeWork
{
public static void NoParameters()
{
}
public static void SingleParameter(string name)
{
}
public static void LotsOfParameters(string name, DateTime Date, int count)
{
}
}
public class StringParameter
{
public string Name { get; set; }
}
public class LotsOfParameters
{
public string Name { get; set; }
public DateTime Date { get; set; }
public int Count { get; set; }
}
}
/*
* Advantages:
* - no additional types required
* Disadvantages
* - not strongly typed
* - requires explicit casting
* - requires "positional" array references
* - no compile time checking for type safety/correct indexing position
* - harder to maintin if parameters change
*/
namespace UsingParams
{
public delegate void NoParametersWrapperDelegate();
public delegate void ParamsWrapperDelegate(params object[] parameters);
public class TimedProcess : IDisposable
{
public TimedProcess()
: this(0)
{
}
public TimedProcess(int interval)
{
if (interval > 0)
InitTimer(interval);
}
public TimedProcess(int interval, NoParametersWrapperDelegate task)
: this(interval, p => task(), null) { }
public TimedProcess(int interval, ParamsWrapperDelegate task, params object[] parameters)
: this(interval)
{
_task = task;
_parameters = parameters;
}
private Timer timer;
private ParamsWrapperDelegate _task;
private object[] _parameters;
public bool InProcess { get; protected set; }
public bool Running
{
get
{
return timer.Enabled;
}
}
private void InitTimer(int interval)
{
if (timer == null)
{
timer = new Timer();
timer.Elapsed += TimerElapsed;
}
timer.Interval = interval;
}
public void InitExecuteProcess()
{
timer.Stop();
InProcess = true;
RunTask();
InProcess = false;
timer.Start();
}
public void RunTask()
{
TimedProcessRunner.RunTask(_task, _parameters);
}
public void TimerElapsed(object sender, ElapsedEventArgs e)
{
InitExecuteProcess();
}
public void Start()
{
if (timer != null && timer.Interval > 0)
timer.Start();
}
public void Start(int interval)
{
InitTimer(interval);
Start();
}
public void Stop()
{
timer.Stop();
}
private bool disposed = false;
public void Dispose(bool disposing)
{
if (disposed || !disposing)
return;
timer.Dispose();
disposed = true;
}
public void Dispose()
{
Dispose(true);
}
~TimedProcess()
{
Dispose(false);
}
}
public static class TimedProcessRunner
{
public static void RunTask(ParamsWrapperDelegate task)
{
RunTask(task, null);
}
public static void RunTask(ParamsWrapperDelegate task, params object[] parameters)
{
task.Invoke(parameters);
}
}
}
/*
* Advantage of this method:
* - everything is strongly typed
* - compile time and "IDE time" verified
* Disadvantages:
* - requires more custom types
*/
namespace UsingGenerics
{
public class TimedProcess : TimedProcess<object>
{
public TimedProcess()
: base() { }
public TimedProcess(int interval)
: base(interval) { }
public TimedProcess(int interval, NoParametersWrapperDelegate task)
: base(interval, task) { }
}
public class TimedProcess<TParam>
{
public TimedProcess()
: this(0)
{
}
public TimedProcess(int interval)
{
if (interval > 0)
InitTimer(interval);
}
public TimedProcess(int interval, NoParametersWrapperDelegate task)
: this(interval, p => task(), default(TParam)) { }
public TimedProcess(int interval, WrapperDelegate<TParam> task, TParam parameters)
: this(interval)
{
_task = task;
_parameters = parameters;
}
private Timer timer;
private WrapperDelegate<TParam> _task;
private TParam _parameters;
public bool InProcess { get; protected set; }
public bool Running
{
get
{
return timer.Enabled;
}
}
private void InitTimer(int interval)
{
if (timer == null)
{
timer = new Timer();
timer.Elapsed += TimerElapsed;
}
timer.Interval = interval;
}
public void InitExecuteProcess()
{
timer.Stop();
InProcess = true;
RunTask();
InProcess = false;
timer.Start();
}
public void RunTask()
{
TaskRunner.RunTask(_task, _parameters);
}
public void TimerElapsed(object sender, ElapsedEventArgs e)
{
InitExecuteProcess();
}
public void Start()
{
if (timer != null && timer.Interval > 0)
timer.Start();
}
public void Start(int interval)
{
InitTimer(interval);
Start();
}
public void Stop()
{
timer.Stop();
}
private bool disposed = false;
public void Dispose(bool disposing)
{
if (disposed || !disposing)
return;
timer.Dispose();
disposed = true;
}
public void Dispose()
{
Dispose(true);
}
~TimedProcess()
{
Dispose(false);
}
}
public delegate void NoParametersWrapperDelegate();
public delegate void WrapperDelegate<TParam>(TParam parameters);
public static class TaskRunner
{
public static void RunTask<TParam>(WrapperDelegate<TParam> task)
{
RunTask(task, default(TParam));
}
public static void RunTask<TParam>(WrapperDelegate<TParam> task, TParam parameters)
{
task.Invoke(parameters);
}
}
}
}
如果委托的方法参数或属性只是Delegate类型,则可以使用它的DynamicInvoke方法来调用委托,无论它的签名是什么。像这样:
public void CallDelegate(Delegate del) {
result = del.DynamicInvoke(1, 2, 3, 'A', 'B', 'C');
}
你真的应该能够使用一个强类型的委托,可能是一个Func或Action,它可以带你需要传递给proccess的任何参数。
public void CallDelegate(Func<int, int, char, char> del) {
result = del(1, 2, 'A', 'B');
}
就个人而言,我会创建一个所有进程必须实现的接口,然后让服务发现实现它的所有对象。这样他们就可以提供他们的计时需求和一种强类型的方法来调用服务。像这样:
//The interface
interface ITimerProcess {
TimeSpan Period {get;}
void PerformAction(string someInfo);
}
//A process
class SayHelloProcess : ITimerProcess {
public TimeSpan Period { get { return TimeSpan.FromHours(1); } }
public void PerformAction(string someInfo) {
Console.WriteLine("Hello, {0}!", someInfo);
}
}
为了简单起见,我将在那里结束,您的服务可以通过查找实现ITimerProcess的类来解决所有过程,然后根据每个公开的Period属性为每个创建一个计时器。计时器只需要调用PerformAction和你想传递的任何其他额外数据。
您在寻找: - params object [] parameterValues
http://msdn.microsoft.com /en-us/library/w5zay9db%28VS.71%29.aspx