Подтвердить что ты не робот

Поиск WCF Duplex "TwoWay" Подписаться + Обратный вызов

Обновление щедрости СНОВА, потому что мне действительно нужно знать, как заставить это работать, или окончательный ответ о том, почему это не будет.

Я добавил альтернативное объяснение проблемы здесь.

Получив чертовски время, получив двухсторонний (IsOneWay = false) клиентский сервер WCF для работы в .Net 3/3.5.

После того, как клиент успешно зарегистрируется в сервисе, служба периодически объявляет() обращается к зарегистрированным клиентам. Теперь клиент или сервер зависают, пока не истечет время отклика сервера SendTimeout, настроенного на 2 секунды. Затем сторона сервера имеет исключение тайм-аута следующим образом. Только тогда код клиента клиента сразу же ПОЛУЧИТ МЕТОД КОДА и попытается вернуть значение. К тому времени клиентский сокет прерывается, а файл WCF терпит неудачу.

Мне кажется, что что-то на клиенте висит локальная очередь WCF от обработки до тех пор, пока сокет не истечет, но не достаточно рано, чтобы отменить вызов локального метода. Но если предположить, что это исключение ниже, сервер пытается отправить операцию на http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous (неуместно!) И отключается, Возможно, этот URI - это просто "Имя" удаленного клиента, так как WCF знает, чтобы ссылаться на него в целях сообщения об ошибке, и это просто означает, что он не загружает URI. Я не могу сказать, сбой сервера сначала или клиент сначала сбой.

Я пробовал добавлять трассировку WCF, но я не получаю гораздо больше информации.

Примерный пример кода здесь, но он, должно быть, слишком много для переваривания. Я экспериментировал с вариациями этого кода.

TimeoutException 'This request operation sent to http://schemas.microsoft.com/2005/12/ServiceModel/Addressing/Anonymous did not receive a reply within the configured timeout (00:00:00).  The time allotted to this operation may have been a portion of a longer timeout.  This may be because the service is still processing the operation or because the service was unable to send a reply message.  Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.'

Server stack trace: 
   at System.ServiceModel.Dispatcher.DuplexChannelBinder.SyncDuplexRequest.WaitForReply(TimeSpan timeout)
   at System.ServiceModel.Dispatcher.DuplexChannelBinder.Request(Message message, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
4b9b3361

Ответ 1

Сначала сделайте копию Программирование WCF Services, если у вас ее еще нет.

Если клиентом является WinForm или WPF, вам нужно использовать [CallbackBehavior(UseSynchronizationContext = false)], так как иначе клиент не будет обрабатывать входящее сообщение, пока поток пользовательского интерфейса не войдет в цикл сообщений.

Во-первых, канал "Дуплекс" в WCF не является действительно дуплексом! Сообщение из

  • Клиент к серверу
  • Может блокировать сообщение, которое сервер ожидает от клиента
  • (или наоборот)

Поскольку сообщения отправляются только по порядку на одном канале WCF. Дуплексный канал WCF НЕ дает вам две очереди входящих сообщений. Результаты, возвращаемые из вызова "TwoWay", являются такими же, как "вызов", как этот уровень стека WCF.. Когда вы получаете голову вокруг этого, многие проблемы становятся понятнее.

Если клиентом является WinForm или WPF, вам может потребоваться использовать [CallbackBehavior(UseSynchronizationContext = false)], так как иначе клиент не будет обрабатывать входящее сообщение, пока поток пользовательского интерфейса не войдет в цикл сообщений.

Некоторые правила, которые я нашел, помогают избежать взаимоблокировок. (Посмотрите на мои вопросы WCF, чтобы увидеть боль, которая у меня была!)

Север никогда не должен вызывать клиент по тому же соединению, что и звонок от того же клиента находится в процесс.

и/или

Клиент не должен переходить к сервера по тому же соединению, что и используется для "обратных вызовов" в то время как обработка обратного вызова.

В следующий раз, когда я думаю, я просто использую два контракта (и, следовательно, TCP-соединения), один для обратного вызова и другие для всех запросов клиент- > сервер. Или используйте мою собственную систему опроса, так как это дало мне столько боли.

Извините, у меня нет времени сегодня, чтобы написать пример. Во всяком случае, большинство примеров работают над тем, что пытается сделать этот пример, но в какой-то мере ломаются в реальной жизни с вашим приложением.

Лучший веб-сайт, который я знаю для примеров WCF, веб-сайт Juval Lowys.

Кроме того, вы можете найти вопросы, которые я задал о WCF при переполнении стека, поскольку у меня были те же проблемы, что и у вас.

Также, проведя день или два, прочитав все вопросы и ответы WCF по переполнению стека, вы получите хорошее представление о проблемах, которые следует избегать.

Ответ 2

Предполагая, что клиент является приложением WinForms, вы должны сделать обработку обратного вызова независимой от остальной части приложения, используя предложение Ian плюс делегирование работы, которая будет выполняться в потоке пользовательского интерфейса, если это необходимо. Например, если сервер хочет уведомить клиента о чем-либо, например, изменить текст метки, вы можете сделать следующее:

[CallbackBehavior(UseSynchronizationContext = false)]
internal class ServiceCallback : IServiceCallback
{
    ChangeMainFormLabel(string text)
    {
        frmMain.Instance.BeginInvoke(new Action()(() => frmMain.Instance.lblSomething.Text = text));
    }
}

(Instance - статическое свойство, возвращающее единственный экземпляр frmMain и lblSomething - это некоторая Label, которую сервер хотел бы изменить.) Этот метод немедленно вернется и освободит сервер от ожидания пользовательский интерфейс клиента и пользовательский интерфейс будут обновлены, как только это станет бесплатным. И лучше всего, никаких взаимоблокировок, поскольку никто не ждет кого-либо.

Ответ 4

Извините, я полностью забыл пример (: - $).

Вот мой код для сервера:
ISpotifyServer.cs

[ServiceContract(CallbackContract = typeof(ISpotifyCallback))]
public interface ISpotifyService
{
    [OperationContract(IsOneWay = true)]
    void Login(string username, string password);
}

ISpotifyCallback.cs

[ServiceContract]
public interface ISpotifyCallback
{
    [OperationContract(IsOneWay = true)]
    void OnLoginComplete();

    [OperationContract(IsOneWay = true)]
    void OnLoginError();
}

Program.cs

class Program
{
    static void Main(string[] args)
    {

        using (ServiceHost host = new ServiceHost(typeof(SpotifyService)))
        {
            host.Open();

            Console.WriteLine("Service running.");
            Console.WriteLine("Endpoints:");

            foreach (ServiceEndpoint se in host.Description.Endpoints)
                Console.WriteLine(se.Address.ToString());


            Console.ReadLine();

            host.Close();
        }
    }
}

AppData.xml

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MetadataEnabledBehavior">
          <serviceMetadata />
          <serviceDebug includeExceptionDetailInFaults="True"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="MetadataEnabledBehavior" name="SpotiServer.SpotifyService">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:9821" />
          </baseAddresses>
        </host>
        <clear />
        <endpoint address="spotiserver" binding="netTcpBinding"
            name="TcpEndpoint" contract="SpotiServer.ISpotifyService"
            listenUriMode="Explicit">
          <identity>
            <dns value="localhost"/>
            <certificateReference storeName="My" storeLocation="LocalMachine"
                x509FindType="FindBySubjectDistinguishedName" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

И для клиента:
Program.cs

class Program
{
    static void Main(string[] args)
    {
        InstanceContext context = new InstanceContext(new CallbackHandler());

        String username;
        String password;

        Console.Write("Username: ");
        username = Console.ReadLine();

        Console.WriteLine("Password: ");
        password = ReadPassword();

        SpotiService.SpotifyServiceClient client = new SpotiService.SpotifyServiceClient(context);
        client.Login(username, password);

        Console.ReadLine();
    }

    private static string ReadPassword()
    {
        Stack<string> passbits = new Stack<string>();
        //keep reading
        for (ConsoleKeyInfo cki = Console.ReadKey(true); cki.Key != ConsoleKey.Enter; cki = Console.ReadKey(true))
        {
            if (cki.Key == ConsoleKey.Backspace)
            {
                //rollback the cursor and write a space so it looks backspaced to the user
                Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                Console.Write(" ");
                Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
                passbits.Pop();
            }
            else
            {
                Console.Write("*");
                passbits.Push(cki.KeyChar.ToString());
            }
        }
        string[] pass = passbits.ToArray();
        Array.Reverse(pass);
        return string.Join(string.Empty, pass);
    }
}

Я думаю, что обо всем. У меня также есть реализация интерфейсов, которые (на стороне клиента) выводят результат на консоль, а на серверах работает "OnLoginComplete", если имя пользователя и пароль верны, в противном случае выполняется "OnLoginError". Дайте мне знать, если это не сработает или вам нужна помощь в настройке.