WCF:尽管调用是异步的,但 DNS 查找会冻结 UI

Posted

技术标签:

【中文标题】WCF:尽管调用是异步的,但 DNS 查找会冻结 UI【英文标题】:WCF: DNS lookup freezes UI despite the call is async 【发布时间】:2018-02-09 05:22:18 【问题描述】:

下面是一个用于演示问题的小型 WPF 测试应用程序。按照设计,它是一个便携式应用程序,可通过 LAN 与其其他实例进行通信。如果我编译它并在远程机器上运行,然后在 localhost 上运行另一个实例,输入远程 PC 的名称并单击“测试连接”它检测到 TCP 上的远程 WCF 服务就好了。但是,如果我输入一些垃圾名称,UI 会冻结几秒钟,然后抛出“主机 blabla 不存在 DNS 条目”。尽管调用应该是异步的。当然,如果我在不同的线程上进行调用,一切都会很顺利。

await Task.Run(async () => await channel.TestConnection());

有没有办法避免Task.Run()?这是关于可扩展性的。如果我需要同时测试成百上千台计算机的在线状态,我希望避免在调用者应用上产生新线程。

XAML:

<Window x:Class="Wcf_Test_Connection.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Wcf_Test_Connection"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent="WidthAndHeight">
    <Grid>
        <StackPanel Margin="20" Width="150">
            <TextBlock Text="Computer name:"/>
            <TextBox x:Name="ComputerNameBox" Margin="0,10"/>
            <Button x:Name="TestConnectionButton" Content="Test Connection" Click="TestConnectionButton_Click" />
        </StackPanel>
    </Grid>
</Window>

WCF 服务(基于通道工厂):

[ServiceContract]
    public interface IAccessPoint
    
        [OperationContract]
        Task<bool> TestConnection();
    

    public class AccessPoint : IAccessPoint
    
        public static int Port = 4848;
        public static string ServiceAddress = "/AccessPoint";

        public static void Configure(ServiceConfiguration config)
        
            ContractDescription contract = ContractDescription.GetContract(typeof(IAccessPoint));

            ServiceEndpoint basicEndpoint = new ServiceEndpoint(contract,
                new NetTcpBinding(), new EndpointAddress("net.tcp://localhost:" + Port.ToString() + ServiceAddress));
            config.AddServiceEndpoint(basicEndpoint);
        

        public static IAccessPoint NewChannel(string address)
        
            NetTcpBinding binding = new NetTcpBinding();
            EndpointAddress endpoint = new EndpointAddress(address);
            ChannelFactory<IAccessPoint> channelFactory = new ChannelFactory<IAccessPoint>(binding, endpoint);
            IAccessPoint channel = channelFactory.CreateChannel();
            return channel;
        

        public Task<bool> TestConnection()
        
            return Task.FromResult(true);
        

代码隐藏:

public partial class MainWindow : Window
    
        public MainWindow()
        
            InitializeComponent();

            var serviceHost = new ServiceHost(typeof(AccessPoint));
            serviceHost.Open();
        

        private async void TestConnectionButton_Click(object sender, RoutedEventArgs e)
        
            this.Background = Brushes.Salmon;

            var channel = AccessPoint.NewChannel("net.tcp://" + ComputerNameBox.Text + ":" + AccessPoint.Port + AccessPoint.ServiceAddress);
            try
            
                bool result = await channel.TestConnection();
                MessageBox.Show("Connection good");
            
            catch (Exception ex)
            
                MessageBox.Show(ex.Message, "Error");
            
            finally
            
                var client = (IClientChannel)channel;
                if (client.State != CommunicationState.Closed && client.State != CommunicationState.Faulted)
                    client.Close();
                else client.Abort();
            

            this.Background = Brushes.White;
        
    

【问题讨论】:

【参考方案1】:

如果我需要同时测试成百上千台计算机的在线状态,我希望避免在调用者应用上产生新线程。

任务池为您动态扩展并发程度。这就是为什么你应该创建任务而不是线程。

有没有办法避免Task.Run()

显然,您需要在后台线程上调用操作,以免阻塞 UI 线程。请记住,async 方法与任何其他方法一样同步运行,直到遇到await。因此,根据async 方法的实际实现方式,它可能仍会阻塞。显然在这种情况下确实如此,所以你最好的选择可能是在这里使用Task.Run。要么是这个,要么是使用另一个 API。

【讨论】:

以上是关于WCF:尽管调用是异步的,但 DNS 查找会冻结 UI的主要内容,如果未能解决你的问题,请参考以下文章

iOS 应用程序在解析异步查询期间冻结

异步函数冻结 UI 线程

同步等待一个异步操作,为啥Wait()会在这里冻结程序

如何异步调用我的 WCF 服务?

突发请求时客户端上的 wcf 死锁

WCF服务需要调用异步