我有以下问题: 在异步上下文中,我需要初始化一些自定义对象的字段,然后才能对其进行其他操作,所以我这样做:
class ContainingObject
{
private CustomObject _co;
SomeMethod()
{
_co = new CustomObject();
_co.InitObjectAsyncCompleted += (s,e) => DoStuff();
_co.InitObjectAsync();
}
}
class CustomObject
{
public string Field1, Field2, Field3, Field4;
public EventHandler InitObjectAsyncCompleted;
public void InitObjectAsync()
{
}
}
问题是字段也是通过对WCF服务的异步调用初始化的,并且必须在我引发InitObjectAsyncCompleted事件之前初始化所有字段。 有很多这些字段,每个字段都使用不同的WCF调用进行初始化,并且暗示我现在无法更改WCF部分,我看到两种方法来解决问题:
1)链WCF调用,因此首先调用初始化第一个字段,然后调用WCF初始化第二个字段,依此类推,然后在所有字段初始化之前,然后在最后一个WCF调用中引发“completed”事件。
public void InitObjectAsync()
{
var proxy = new ProxyFactory.GetCustomObjectProxy;
proxy.GetDataForField1Completed += (s,e) =>
{
Field1 = e.Result;
proxy.GetDataForField2Completed += (s1,e1) =>
{
Field2 = e1.Result;
//keep this up building a chain of events, when Field4 is filled, raise
// InitObjectAsyncCompleted(this, null);
};
proxy.GetDataForField2();
};
proxy.GetDataForField1();
}
2)因为我知道应该完成多少个方法调用,在这种情况下,我可以制作一个计数器。
public void InitObjectAsync()
{
int counter = 0;
var proxy = new ProxyFactory.GetCustomObjectProxy;
proxy.GetDataForField1Completed += (s,e) =>
{
Field1 = e.Result;
if(counter >= 3)
InitObjectAsyncCompleted(this, null);
else
counter++;
};
proxy.GetDataForField1();
proxy.GetDataForField2Completed += (s,e) =>
{
Field2 = e.Result;
if(counter >= 3)
InitObjectAsyncCompleted(this, null);
else
counter++;
};
proxy.GetDataForField2();
//repeat for all fields
}
我真的不喜欢任何一种解决方案,第一种是构建一个非常大且可读性很差的事件链,其次只是......原始 - 任何人都可以提出更优雅的方法来解决这个问题吗?
答案 0 :(得分:3)
如果您使用.NET 4.0的并行扩展,您可以创建多个异步任务并轻松加入它们:
Task[] tasks = new Task[3]
{
Task.Factory.StartNew(() => MethodA()),
Task.Factory.StartNew(() => MethodB()),
Task.Factory.StartNew(() => MethodC())
};
//Block until all tasks complete.
Task.WaitAll(tasks);
答案 1 :(得分:1)
你的第二种方法比第一种方法更容易理解,但两种方法都有点脆弱。
一种替代方法是跟踪未完成的初始化请求和完成次数,并使用此信息来决定何时触发事件。这是我的意思的一个例子:
private int _outstandingRequests = 0;
public void InitObjectAsync()
{
RequestField( proxy.GetDataForField1,
proxy.GetDataForField1Completed,
s => Field1 = s );
RequestField( proxy.GetDataForField2,
proxy.GetDataForField2Completed,
s => Field2 = s );
RequestField( proxy.GetDataForField3,
proxy.GetDataForField3Completed,
s => Field3 = s );
// ... and so on...
}
// This method accepts two actions and a event handler reference.
// It composes a lambda to perform the async field assignment and internally
// manages the count of outstanding requests. When the count drops to zero,
// all async requests are finished, and it raises the completed event.
private void RequestField<T>( Action fieldInitAction,
EventHandler fieldInitCompleteEvent,
Action<T> fieldSetter )
{
// maintain the outstanding request count...
_outstandingRequests += 1;
// setup event handler that responds to the field initialize complete
fieldInitCompleteEvent += (s,e) =>
{
fieldSetter( e.Result );
_outstandingRequests -= 1;
// when all outstanding requests finish, raise the completed event
if( _outstandingRequests == 0 )
RaiseInitCompleted();
}
// call the method that asynchronously retrieves the field value...
fieldInitAction();
}
private void RaiseInitCompleted()
{
var initCompleted = InitObjectAsyncCompleted;
if( initCompleted != null )
initCompleted(this, null);
}
答案 2 :(得分:0)
将每个WCF调用放在一个小的包装类中。将这些类放在一个集合(或者如果顺序很重要的列表中),并在调用完成后让它们从集合中删除。他们还应该给监视器发出脉冲。
Monitor.Enter。循环遍历集合中的所有WCF调用。然后在监视器上等待。每次收到通知时,如果该集合不为空,请等待。当你退出等待循环时,调用init并引发事件。你总是可以在Monitor.Wait上超时(如果你愿意的话)(我经常调用我的锁waitingRoom
,这样很明显发生了什么。)
如果你把自己与你正在等待的WCF调用这一事实隔离开来,那么这也是很好且易于测试的,并且你可以通过包装类识别它来记录任何失败的WCF调用。