请考虑以下代码:
class A
{
private int _workId;
public void DoWork()
{
_workId = new Random().Next(100);
Console.WriteLine("Starting a long process ID: " + _workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle += OnLongProcessWorkMiddle;
Console.WriteLine("At end of long process: " + _workId);
longProcess.OnWorkMiddle -= OnLongProcessWorkMiddle;
}
private void OnLongProcessWorkMiddle(object sender, EventArgs e)
{
Console.WriteLine("In middle of long process: " + _workId);
}
}
以下代码在我看来是有问题的,原因如下:
Console.WriteLine("In middle of long process: " + _workId);
出现在Console.WriteLine("At end of long process: " + _workId);
旁边,然而,它们都出现在单独的函数中。_workId
应该是 DoWork 中的所有功能,但是,我不得不使其成为一个字段函数,以便能够从回调内部访问该信息。每个附加参数或返回值也将成为字段成员。有一些解决方案可以减少此代码中的问题数量:
将函数及其回调封装在不同的类中
我上面描述的一个问题是字段成员,它们不一定与所有者类相关,而是特定于函数 DoWork 及其回调。通过将函数封装在自己的类中,我们删除了不必要的混乱,并将其分组到自己的逻辑组中。缺点是它需要创建一个类,从功能上来说应该是一个功能。它也没有解决问题#1。
使用匿名代表保持单一功能
我们可以像这样重写代码:
public void DoWork()
{
var workId = new Random().Next(100);
EventHandler onLongProcessWorkMiddle = delegate { Console.WriteLine("In middle of long process: " + workId); };
Console.WriteLine("Starting a long process ID: " + workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle += onLongProcessWorkMiddle;
Console.WriteLine("At end of long process: " + workId);
longProcess.OnWorkMiddle -= onLongProcessWorkMiddle;
}
现在我们不再需要任何现场成员,我们可以将所有内容完美地保存在一个功能中。但是,在可读性方面,我发现代码相当缺乏,因为稍后执行的代码(或者更确切地说是代理)显示在顶部。
使用半通用助手类
在这个解决方案中,我编写了一个半通用的辅助类来同步执行。它允许我以下列方式编写代码:
public void DoWork()
{
var workId = new Random().Next(100);
LongProcess longProcess = new LongProcess();
var syncr = new EventSyncr();
syncr.Sync(
delegate { longProcess.OnWorkMiddle += syncr.EventCallback; },
delegate { longProcess.StartWork(); },
delegate { Console.WriteLine("In middle of long process: " + workId); },
delegate { longProcess.OnWorkMiddle -= syncr.EventCallback; });
Console.WriteLine("At end of long process: " + workId);
}
以下是帮助程序类的代码:
/// <summary>
/// Used to synchronize code that waits for a callback from a function.
/// </summary>
class EventSyncr
{
readonly AutoResetEvent _eventCallbackEvent = new AutoResetEvent(false);
private Action _actionAtEvent;
/// <summary>
/// Executes the syncing process. This function is blocked until the event is called back (once), then it executes the 'actionAtEvent' segment and
/// after unsubscription, exists.
/// </summary>
/// <param name="eventSubscription">The passed delegate must subscribe the event to EventSyncr.EventCallback.</param>
/// <param name="eventCausingAction">The event causing action.</param>
/// <param name="actionAtEvent">The action at event.</param>
/// <param name="eventUnsubscription">Unsubscribe the event that was subscribed in the first parameter</param>
public void Sync(Action eventSubscription, Action eventCausingAction, Action actionAtEvent, Action eventUnsubscription)
{
_actionAtEvent = actionAtEvent;
try
{
eventSubscription();
eventCausingAction();
_eventCallbackEvent.WaitOne();
}
finally
{
eventUnsubscription();
}
}
public void EventCallback(object sender, EventArgs e)
{
try
{
_actionAtEvent();
}
finally
{
_eventCallbackEvent.Set();
}
}
}
虽然有点冗长,但在我看来,它解决了我在上面提出的所有问题:
代码按其逻辑顺序显示其执行方式,并且所有其他属性都是自包含的。
我很好奇你对这个问题和可能的解决方案的看法。哪个看起来最好?有没有更好的方法来解决这个问题?
我知道在C#5中,等待功能将提供更好的解决方案,但我们还没有完全实现:(。
谢谢!
答案 0 :(得分:1)
您的EventSyncr课程太奇怪了,您应该获得额外奖励, 只是开玩笑;)
但实际上,您只需创建一个AutoResetEvent并使用WaitHandle.WaitAll在代码末尾等待它。
在异步方法结束时,您必须发出AutoResetEvent信号,主线程知道您的异步进程刚刚完成。
public void DoWork()
{
var workId = new Random().Next(100);
Console.WriteLine("Starting a long process ID: " + workId);
LongProcess longProcess = new LongProcess();
longProcess.StartWork();
longProcess.OnWorkMiddle += ()=>{ Console.WriteLine("In middle of long process: " + workId);
longProcess.OnWorkEnd += ()=>{
Console.WriteLine("At the end of a long process");
autoEvent.Set();
};
WaitHandle.WaitAll(new []{longProcess.autoEvent});
}