Получение недопустимого объекта из вызова LyncClient.GetClient()

ПРИМЕЧАНИЕ. Обновлено решением внизу вопроса.

У меня возникли проблемы с приложением, использующим Lync 2013 SDK. Вот поведение, которое я вижу:

  • Если Lync уже запущен, когда я запускаю свое приложение, то вызов LyncClient.GetClient() вернет действительный клиентский объект.
  • Если Lync не запущен, когда я запускаю свое приложение, вызов LyncClient.GetClient() вызовет ClientNotFoundException. Я могу обработать исключение и запустить таймер для проверки связи с клиентом. Позже, когда я запущу Lync, LyncClient.GetClient() вернет действительный клиентский объект.
  • Если Lync завершает работу во время работы моего приложения, я могу обнаружить эту ситуацию несколькими способами и запустить таймер для проверки связи, чтобы клиент вернулся.

Пока все хорошо, но вот где возникают проблемы:

  • Если Lync отключается во время работы моего приложения, то последующие вызовы LyncClient.GetClient() кажутся возвращающими допустимый клиентский объект (даже если Lync не запущен), но попытки вызова этого объекта вызывают InvalidCastException.
  • Даже после перезапуска Lync последующие вызовы LyncClient.GetClient() по-прежнему возвращают объект, который выдает InvalidCastException при попытке доступа к нему.

Детали исключения:

Невозможно привести COM-объект типа «System.__ComObject» к типу интерфейса «Microsoft.Office.Uc.IClient». Эта операция завершилась неудачно, так как вызов QueryInterface компонента COM для интерфейса с IID "{EE9F3E74-AC61-469E-80D6-E22BF4EEFF5C}" завершился неудачно из-за следующей ошибки: сервер RPC недоступен. (Исключение из HRESULT: 0x800706BA).

Я попробовал рекомендации здесь: Устранение неполадок при разработке Lync 2013 SDK. Кажется, это не имеет никакого значения. Я продолжаю получать недопустимый объект клиента в течение многих минут после запуска Lync и повторного входа в систему.

Это происходит не каждый раз. Проблема, по-видимому, довольно часто возникает, если Lync уже запущен, когда мое приложение запускается (т. е. самый первый вызов LyncClient.GetClient() завершается успешно). уже работает (т.е. первая попытка GetClient() не удалась.)

Кто-нибудь еще видел это раньше? Какие-либо предложения?


Обновление с попыткой выгрузки AppDomain

Я попытался получить клиентский объект с помощью этого кода, но поведение точно такое же:

public class LyncClientProvider
{
    private AppDomain _domain = CreateDomain();

    public LyncClient GetLyncClient()
    {
        if (_domain == null) CreateDomain();
        var client = GetClient();
        if (client != null && client.Capabilities != LyncClientCapabilityTypes.Invalid) return client;

        // Reload AppDomain
        if (_domain != null) AppDomain.Unload(_domain);
        _domain = CreateDomain();
        return GetClient();
    }

    private LyncClient GetClient()
    {
        if (_domain == null) return null;
        return ((InternalProvider)
            _domain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
                typeof (InternalProvider).FullName)).GetLyncClient();
    }

    private static AppDomain CreateDomain()
    {
        return AppDomain.CreateDomain("LyncClientCreator", null, new AppDomainSetup
        {
            ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
            ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
        });
    }

    [Serializable]
    private class InternalProvider
    {
        public LyncClient GetLyncClient()
        {
            try
            {
                return LyncClient.GetClient();
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}

Пример решения с использованием выделенного потока

Благодаря вкладу от djp я реализовал этот простой класс для предоставления LyncClient, и теперь переподключения работают правильно.

public class LyncClientProvider
{
    private Thread _thread;
    private volatile bool _running;
    private volatile bool _clientRequested;
    private volatile LyncClient _lyncClient;

    private void MakeClientLoop()
    {
        while (_running)
        {
            if (_clientRequested) MakeClient();
            Thread.Sleep(100);
        }
    }

    private void MakeClient()
    {
        try
        {
            _lyncClient = LyncClient.GetClient();
        }
        catch (Exception)
        {
            _lyncClient = null;
        }
        _clientRequested = false;
    }

    public void Start()
    {
        if (_running) return;
        _running = true;
        _thread = new Thread(MakeClientLoop);
        _thread.Start();
    }

    public void Stop()
    {
        if (!_running) return;
        _running = false;
        _thread.Join();
        _clientRequested = false;
    }

    public LyncClient GetLyncClient()
    {
        if (!_running) return null;
        _clientRequested = true;
        while (_clientRequested) Thread.Sleep(100);
        return _lyncClient;
    }
}

person danBhentschel    schedule 05.06.2015    source источник
comment
public static LyncClient GetClient - static меня вот смущает. Если реализация этого метода кэширует COM-объект в лениво инициализируемой статической переменной и нет возможности аннулировать кешированное значение, то единственный способ — создать другой домен приложения, создать этот метод в этом домене и выгрузить домен, когда Lync был закрыт и вы поймали исключение. Вы сравнивали ссылки, возвращенные GetClient? Они одинаковы? Есть ли способ, который позволяет сбросить/пересоздать значение клиента?   -  person Dennis    schedule 05.06.2015
comment
Конечно, похоже, что я получаю кешированный объект. Распечатка значения GetHashCode() выводит одно и то же значение снова и снова, когда я получаю плохой объект. OTOH, я каждый раз получаю уникальный хеш-код в сценарии, где переподключения работают правильно. Я не могу найти способ сбросить кеш.   -  person danBhentschel    schedule 05.06.2015
comment
Я ничего не знаю о Lync, поэтому это только предположение. Если моя догадка верна, дайте мне знать, чтобы преобразовать ее в ответ. Я считаю, что при каждом запуске LyncClient.GetClient следует сохранять уникальное удостоверение на основе результата. Когда вы перехватываете InvalidCastException во время работы LyncClient, GetClient, вы должны пометить уникальный идентификатор как недопустимый. Всякий раз, когда уникальный идентификатор, полученный от GetClient, изменяется, сбрасывайте его действительность. Если в какой-то момент действительность недействительна, обработайте ее соответствующим образом.   -  person Lajos Arpad    schedule 05.06.2015
comment
Спасибо, но я думаю, что либо вы меня неправильно поняли, либо я неправильно понял ваш комментарий. Моя проблема в том, что как только я попаду в эту ситуацию, возврат из GetClient() никогда не изменится, пока я не перезапущу программу. Это не проблема признания действительного результата. Проблема в том, что результат всегда недействителен при определенной последовательности событий.   -  person danBhentschel    schedule 05.06.2015
comment
@danBhentschel: Боюсь, единственный способ выгрузить домен приложения.   -  person Dennis    schedule 05.06.2015
comment
Спасибо еще раз. Это было решение, которое я не рассматривал. Я только что попробовал (в отредактированном вопросе выше), но, как ни странно, результат тот же. Если Lync уже запущен, я не могу повторно подключиться после отключения. Если он не запускается при запуске, я могу снова подключиться. Я сделал что-то не так в своей реализации AppDomain? Я вижу, как он проходит через код для выгрузки и воссоздания AppDomain. Любые другие предложения? Я рассматриваю возможность создания отдельного процесса и использования WCF через именованный канал для прокси-запросов к LyncClient. Хотя это довольно некрасиво.   -  person danBhentschel    schedule 05.06.2015
comment
Я запускаю таймер, чтобы проверить наличие lync, аналогично тому, что вы делаете. Для получения экземпляра LyncClient требуется несколько секунд, но не минут. Не уверен, но это может быть проблема с машиной. Может попробовать проделать ту же операцию на какой-нибудь другой машине?   -  person Ankit Vijay    schedule 06.06.2015


Ответы (3)


У меня была та же проблема, исправление для меня состоит в том, чтобы убедиться, что каждый вызов LyncClient.GetClient() происходит в одном и том же потоке.

person djp    schedule 10.07.2015
comment
Большое спасибо, что нашли время ответить! На самом деле я немного отложил эту проблему и полностью намеревался воскресить проблему, когда приду на работу сегодня утром. Ваш ответ очень своевременный и очень полезный. Сегодня утром я реализовал простой класс LyncClientProvider, который гарантирует, что LyncClient всегда извлекается одним и тем же потоком, и теперь все работает отлично. Спасибо еще раз. Я принял ваш ответ. - person danBhentschel; 10.07.2015

«• Если Lync отключается во время работы моего приложения, то последующие вызовы LyncClient.GetClient() возвращают допустимый клиентский объект (даже если Lync не запущен), но попытки вызова этого объекта вызывают исключение InvalidCastException».

Не могли бы вы уточнить, что вы подразумеваете под «если Lync исчезнет». Вы имеете в виду, что вышли из Lync или вышли из Lync (т. е. процесс Lync не запущен)? Если вы только что вышли из Lync, LyncClient не будет выдавать исключение. Вы можете прослушать событие LyncClient.StateChanged и проверить событие clientStateChangedEventArgs.NewState.

private void LyncClient_StateChanged(object sender, ClientStateChangedEventArgs e) {
        switch (e.NewState) {
                case ClientState.Invalid:
                case ClientState.ShuttingDown:                  
                    this.IsLyncStarted = false;
                    this.IsLyncSignedIn = false;
                    break;
                case ClientState.SignedOut:
                case ClientState.SigningIn:
                case ClientState.SigningOut  
                    this.IsLyncStarted = true;
                    this.IsLyncSignedIn = false;
                    break;
                case ClientState.SignedIn:
                    this.IsLyncStarted = true;
                    this.IsLyncSignedIn = true;
                    break;
            }
            if (!this.IsLyncStarted || !this.IsLyncSignedIn) {                    
                // Do relevant operation
                // Show error message to user - Lync is not present
            }
    }

Когда пользователь выходит из Lync, я запускаю таймер, аналогичный вашему подходу, и пытаюсь инициализировать Lync, как показано ниже:

private void InitializeLync() {
        try {
            if (this.LyncClient == null || this.LyncClient.State == ClientState.Invalid) {
                this.LyncClient = LyncClient.GetClient();

                if (this.LyncClient != null) {
                    this.LyncClient.StateChanged += this.LyncClient_StateChanged;       
                }
            }

        } catch (Exception ex) {
            // Show message to user that Lync is not started
        }
}

Это работает для меня. Возможно, это проблема конкретной машины.

person Ankit Vijay    schedule 06.06.2015

Я предполагаю, что вас отключили от сервера Lync. Метод LyncClient.GetClient() может по-прежнему работать, поскольку у вас есть некоторые устаревшие переменные, но вы не сможете выполнять с его помощью какие-либо действия Lync.

Я думаю, что просмотр вещей подавления пользовательского интерфейса поможет. https://msdn.microsoft.com/en-us/library/office/hh345230%28v=office.14%29.aspx

person jrs    schedule 05.06.2015
comment
Спасибо, но я действительно не хочу подавлять пользовательский интерфейс клиента. Я очень хочу, чтобы интерфейс клиента Lync продолжал нормально работать. Мое приложение работает совместно с Lync. Это не замена. - person danBhentschel; 05.06.2015