WPF で WCF サービスのホスト兼クライアントを作成してハマった

はじめに

大人の事情により、 WPF アプリケーションで WCF サービスのホスト兼クライアントを試すことになりました。

WPF アプリケーションに WCF サービスをホストさせます

App.xaml
<Application x:Class="WcfHostSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Application>
App.xaml.cs
using System.ServiceModel;
using System.Windows;

namespace WcfHostSample
{
    public partial class App : Application
    {
        public const string address = "net.pipe://localhost/Greeting";
        private ServiceHost host = null;

        protected override void OnStartup(StartupEventArgs e)
        {
            host = new ServiceHost(typeof(GreetingService));
            host.AddServiceEndpoint(typeof(IGreetingService),
                new NetNamedPipeBinding(),
                address);
            host.Open();

            MainWindow = new Window1();
            MainWindow.Show();

            base.OnStartup(e);
        }

        protected override void OnExit(ExitEventArgs e)
        {
            if (host != null)
            {
                host.Close();
            }
            base.OnExit(e);
        }
    }

    [ServiceContract]
    public interface IGreetingService
    {
        [OperationContract]
        string Greet(string name);
    }

    public class GreetingService : IGreetingService
    {
        public string Greet(string name)
        {
            return string.Format("Hello, {0}!", name);
        }
    }
}

WCF サービスはあいさつ文を返す簡単なもの。WPF メインウィンドウを表示する前に、App クラス内で WCF サービスを起動しています。

自分でホストした WCF サービスを呼び出してみます

Window1.xaml
<Window x:Class="WcfHostSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <Label Content="名前:"/>
        <TextBox x:Name="_nameTextBox"/>
        <Label Content="結果"/>
        <TextBox x:Name="_resultTextBox"
                 IsReadOnly="True"/>
        <Button Content="あいさつする!"
                Click="Button_Click"/>
    </StackPanel>
</Window>
Window1.xaml.cs
using System;
using System.ServiceModel;
using System.Threading;
using System.Windows;

namespace WcfHostSample
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            string name = _nameTextBox.Text;
            if (string.IsNullOrEmpty(name))
            {
                MessageBox.Show("名前を入力して下さい。");
                return;
            }

            // UI スレッドで実行すると固まる。なぜ?
            //IGreetingService proxy = ChannelFactory<IGreetingService>.CreateChannel(
            //    new NetNamedPipeBinding(),
            //    new EndpointAddress(App.address));
            //_resultTextBox.Text = proxy.Greet(name);
            //((IClientChannel)proxy).Close();

            // 別スレッドで実行すると上手くいく
            Thread thread = new Thread((ThreadStart)delegate
            {
                IGreetingService proxy = ChannelFactory<IGreetingService>.CreateChannel(
                    new NetNamedPipeBinding(),
                    new EndpointAddress(App.address));
                string result = proxy.Greet(name);

                Dispatcher.BeginInvoke(new Action(() =>
                {
                    _resultTextBox.Text = result;
                }));

                ((IClientChannel)proxy).Close();
            });
            thread.Start();
        }
    }
}

Button がクリックされたら、WCF サービスを呼び出しています。

WCF サービス呼び出しでハマった

上記サンプルコード内にコメントで書いていますが、UI スレッド上で WCF サービスを呼び出すと、Greet メソッドのところで固まってしまいました。そして TimeoutException 発生。

試しに、別スレッドで WCF サービスを呼び出してみると、固まることなく WCF サービスから結果を取得できました。

ホストとクライアントが別 exe だと、UI スレッドで呼び出しても上手くいくので、両方を1つの WPF で兼ねているのが問題なんだろう。でも、詳しい原因がわからない……。ネットで調べても、こんな事をやる人はいないみたい。

もう少し調べてみます

でも、そろそろタイムリミットかも。UI スレッドとは違うスレッドで WCF サービスを呼び出して回避できることは分かったけれど、原因が分らないままだと気持ち悪い。