我有一个wcf客户端,在与wcf服务进行一些复杂的交互之前,只需检查服务是否存活。由于连接失败的超时是一分钟,我想异步实现此检查。
这就是我所拥有的:
//Method on the main UI thread
public void DoSomeComplicatedInteraction()
{
If(ConnectionIsActive()) DoTheComplicatedInteraction();
}
private bool ConnectionIsActive()
{
//Status is a Textblock on the wpf form
Status.Text = "Checking Connection";
var ar = BeginConnectionIsActive();
while (!ar.IsCompleted)
{
Status.Text += ".";
Thread.Sleep(100);
}
EndConnectionIsActive(ar);
//IsConnected is a boolean property
return IsConnected;
}
private IAsyncResult BeginConnectionIsActive()
{
//checkConnection is Func<bool> with value CheckConnection
return checkConnection.BeginInvoke(null,checkConnection);
}
private void EndConnectionIsActive(IAsyncResult ar)
{
var result=((Func<bool>)ar.AsyncState).EndInvoke(ar);
IsConnected = result;
}
private bool CheckConnection()
{
bool succes;
try
{
//synchronous operation
succes=client.Send(RequestToServer.GetPing()).Succes;
}
catch
{
succes = false;
}
return succes;
}
这很有效。只是,当我尝试通过在服务器的Send方法中添加Thread.Sleep来模拟慢速服务器响应时,UI变得无法响应。而且,状态文本块的文本没有明显更新。 看来我需要某种Application.DoEvents方法。 或者我需要一种不同的方法吗?
编辑: 实际上,需要采用不同的方法。使用Thread.Sleep将在我们调用主UI线程时阻止UI。 以下是我解决它的方法:
//Method on the main UI thread
public void DoSomeComplicatedInteraction()
{
IfConnectionIsActiveDo(TheComplicatedInteraction);
}
private void TheComplicatedInteraction()
{...}
private void IfConnectionIsActiveDo(Action action)
{
Status.Text = "Checking Connection";
checkConnection.BeginInvoke(EndConnectionIsActive,
new object[] { checkConnection, action });
}
private void EndConnectionIsActive(IAsyncResult ar)
{
var delegates = (object[]) ar.AsyncState;
var is_active_delegate = (Func<bool>) delegates[0];
var action = (Action) delegates[1];
bool is_active=is_active_delegate.EndInvoke(ar);
IsConnected = is_active;
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<bool>(UpdateStatusBarToReflectConnectionAttempt),is_active);
if (is_active) action();
}
我对此并不满意:它将以前用两种方法传播的(同步)代码扩展为五种!这使得代码非常混乱......
答案 0 :(得分:3)
这是因为你在循环中等待完成操作。此循环在主线程上完成,因此UI被冻结。相反,您应该将AsyncCallback
传递给BeginInvoke
(而不是null),以便在操作完成时通知您。
以下是我将如何实现它:
public void BeginConnectionIsActive()
{
AsyncCallback callback = (ar) =>
{
bool result = checkConnection.EndInvoke(ar);
// Do something with the result
};
checkConnection.BeginInvoke(callback,null);
}
答案 1 :(得分:2)
我无法从您的代码示例中确切地说明您调用它的位置,但一般情况下,您应该从不在UI线程上执行阻止工作。如您所述,这将导致UI无响应。你必须在后台完成这项工作。我的猜测是你在UI线程上直接运行ConnectionIsActive循环,这意味着在你从该方法返回之前,UI状态更新基本上会被阻止。
看起来你有正确的想法(使用异步方法),但实现似乎过于复杂。
方便的是,即使服务器实现不使用异步方法,WCF也允许您拥有异步客户端契约。合同只需要具有相同的合同名称,并专门为异步模式标记,如下例所示:
[ServiceContract(Name = "Ping")]
interface IPing
{
[OperationContract(IsOneWay = false)]
void Ping(string data);
}
[ServiceContract(Name = "Ping")]
interface IPingClient
{
[OperationContract(AsyncPattern = true, IsOneWay = false)]
IAsyncResult BeginPing(string data, AsyncCallback callback, object state);
void EndPing(IAsyncResult result);
}
现在,在客户端,您可以使用IPingClient
合同,只需拨打client.BeginPing(...)
即可。当所有实际工作在后台完成时,它会立即返回,可选择在完成后回电。
您的代码可能如下所示:
void SomeCodeInUIThread()
{
// ...
// Do something to the UI to indicate it is
// checking the connection...
client.BeginPing("...some data...", this.OnPingComplete, client);
}
void OnPingComplete(IAsyncResult result)
{
IPingClient client = (IPingClient)result.AsyncState;
try
{
client.EndPing(result);
// ...
}
catch (TimeoutException)
{
// handle error
}
// Operation is complete, so update the UI to indicate
// you are done. NOTE: You are on a callback thread, so
// make sure to Invoke back to the main form.
// ...
}
答案 2 :(得分:1)
使用类似的东西:
public static class Run
{
public static void InBackround<TResult>(Func<TResult> operation, Action<TResult> after)
{
Async(operation, after, DispatcherPriority.Background);
}
private static void Async<TResult>(Func<TResult> operation, Action<TResult> after,
DispatcherPriority priority)
{
var disp = Dispatcher.CurrentDispatcher;
operation.BeginInvoke(delegate(IAsyncResult ar)
{
var result = operation.EndInvoke(ar);
disp.BeginInvoke(priority, after, result);
}, null);
}
}
并称之为:
Run.InBackround(CheckConnection, DoTheComplicatedInteraction);
需要一些委托,在后台线程中调用它,并在完成后返回值并在UI线程中调用'after'委托。
在上面的示例中,DoTheComplicatedInteraction应如下所示:
DoTheComplicatedInteraction(bool connected) {
if(connected) { proceed... } else { retry... }
}
如果您对Dispatcher感到困惑,请阅读msdn。
上的Build More Responsive Apps With The Dispatcher文章