2015-10-15 3 views
0

У нас есть WPF с элементом управления вкладками, являющимся основным пользовательским интерфейсом для отображения информации о клиенте, каждый открытый клиент получает свою собственную вкладку. На вкладках клиентов у нас есть еще один элемент управления вкладкой, который позволяет переключаться между различными подмножествами информации, 2 из них используют элемент управления Webbrowser с функциональностью IHTMLChangeSink для мониторинга скрытых div, предназначенных для запуска логики в приложении.IHTMLChangeSink UnregisterForDirtyRange throwing ComException HRESULT E_FAIL

Раньше мы испытывали очень большую утечку памяти, когда вкладка Customer была закрыта, причиной этого было обнаружение обработчика событий, созданного RegisterForDirtyRange. Чтобы устранить утечку памяти, методы Dispose были изменены для вызова UnregisterForDirtyRange, используя AutoIT для быстрого открытия и закрытия вкладок клиентов, мы смогли доказать, что утечка памяти была исправлена; это было сделано на машине класса разработчиков.

После того, как это изменение было перенесено на тестеры, мы начали видеть сбой приложения. В журнале событий мы увидели, что вызов UnregisterForDirtyRange выбрасывал ComException с помощью HRESULT E_FAIL. Поскольку мы никогда не видели, чтобы это возникало на оборудовании разработчика и на машинах тестеров, не было гарантированного способа создания сбоя. Я думаю, что есть какое-то состояние гонки, которое усиливается при работе на менее мощном оборудовании.

Учитывая эту информацию, мой вопрос касается внутренней работы вызова Unregister, может ли кто-нибудь подумать о том, что может вызвать это исключение?

Моя первоначальная мысль заключалась в том, что, возможно, метод Notify работал во время утилизации, поэтому я попытался ввести блокировку между dispose и notify, но это ничего не изменило.

Вот урезанная версия вкладок, которая надстроена веб-браузер:

public partial class BrowserTabWidget : BrowserWidget, IHTMLChangeSink 
{ 
    private static Guid _markupContainer2Guid = typeof(IMarkupContainer2).GUID; 
    private IMarkupContainer2 _container; 
    private uint _cookie; 

    public BrowserTabWidget() 
    { 
     InitializeComponent(); 
     if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) 
     { 
      Loaded += OnLoaded; 
     } 
    } 

    protected override void DisposeControls() 
    { 
     if (_container != null) 
     { 
      _container.UnRegisterForDirtyRange(_cookie); 
      Marshal.ReleaseComObject(_container); 
     } 
     WebBrowser.LoadCompleted -= OnWebBrowserLoadCompleted; 
     WebBrowser.Dispose(); 
    } 

    public override string CurrentUri 
    { 
     get { return (string)GetValue(CurrentUriProperty); } 
     set 
     { 
      NavigateTo(value); 
      SetValue(CurrentUriProperty, value); 
     } 
    } 

    private void NavigateTo(string value) 
    { 
     WebBrowser.Navigate(new Uri(value)); 
    } 

    public static readonly DependencyProperty CurrentUriProperty = DependencyProperty.Register("CurrentUri", typeof(string), typeof(BrowserTabWidget), new FrameworkPropertyMetadata(CurrentUriChanged)); 
    public static void CurrentUriChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var widget = (BrowserTabWidget)d; 
     d.Dispatcher.BeginInvoke(
      DispatcherPriority.Normal, 
      new Action(() => widget.NavigateTo(e.NewValue.ToString()))); 
    } 

    private void InitializeWebBrowser() 
    { 
     WebBrowser.LoadCompleted += OnWebBrowserLoadCompleted; 
     WebBrowser.Navigate(new Uri(viewModel.InitialUrl)); 
    } 

    void OnWebBrowserLoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e) 
    { 
     _container = GetMarkupContainer(); 
     _container.RegisterForDirtyRange(this, out _cookie); 
    } 

    private void OnLoaded(object sender, RoutedEventArgs e) 
    { 
     Loaded -= OnLoaded; 
     Load(); 
    } 

    private void Load() 
    { 
     InitializeWebBrowser(); 
    } 

    private IMarkupContainer2 GetMarkupContainer() 
    { 
     var oDocument = WebBrowser.Document as IHTMLDocument2; 
     var pDocument = Marshal.GetIUnknownForObject(oDocument); 
     IntPtr pMarkupContainer; 
     Marshal.QueryInterface(pDocument, ref _markupContainer2Guid, out pMarkupContainer); 
     var oMarkupContainer = Marshal.GetUniqueObjectForIUnknown(pMarkupContainer); 
     Marshal.Release(pDocument); 
     Marshal.Release(pMarkupContainer); 
     return (IMarkupContainer2)oMarkupContainer; 
    } 

    public void Notify() 
    { 
     var document = WebBrowser.Document as HTMLDocument; 
     if (document != null) 
     { 
      //Parse Dom for hidden elements and trigger appropriate event handler 
     } 
    } 
} 

ответ

1

Hmya, E_FAIL, проклятие COM. Бесполезно когда-либо диагностировать что-либо, это всего лишь класс учителя для качества сообщения об ошибках. Я написал тот же код в Winforms, чтобы получить что-то проверяемое, без воспроизведения. Тем не менее очень легко заставить воспроизвести. Учитывая, что метод принимает только один аргумент, есть только одна вещь, которая может пойти не так:

if (_container != null) 
    { 
     _container.UnRegisterForDirtyRange(_cookie); 
     _container.UnRegisterForDirtyRange(_cookie); // Kaboom!!! 
     Marshal.ReleaseComObject(_container); 
    } 

Bad печенье. Преступник, что они не вернут E_INVALIDARG кстати.

Я не мог проверить ваш точный код, конечно, у него есть проблемы. Наиболее серьезный, который я вижу, и проблема с воспроизведением заключается в том, что нет защиты от вызова DisposeControls() более одного раза. В вообще никогда не ошибочно удалять объекты более одного раза, я не знаю, если это реалистичный режим отказа в вашем проекте. В противном случае очень просто защитить себя от этого. В том числе всеобъемлющая и проглатыванием код:

protected override void DisposeControls() 
{ 
    if (_container == null) return; 
    try { 
     _container.UnRegisterForDirtyRange(_cookie); 
    } 
    catch (System.Runtime.InteropServices.COMException ex) { 
     if (ex.ErrorCode != unchecked((int)0x80004005)) throw; 
     // Log mishap... 
    } 
    finally { 
     Marshal.ReleaseComObject(_container); 
     _container = null; 
     _cookie = 0; 
     WebBrowser.LoadCompleted -= OnWebBrowserLoadCompleted; 
     WebBrowser.Dispose(); 
    } 
} 

Еще одна вещь, которую я заметил в моей тестовой версии кода является то, что вы, кажется, не имеют никакой защиты от браузера перейти на другую страницу с помощью любых других средств, чем WebBrowser .Navigate(). Или событие LoadCompleted, запускаемое более одного раза, например, для домашней страницы stackoverflow.com. Или любую веб-страницу, использующую фреймы. Это утечка. Сделайте его устойчивым, если обработчик события OnWebBrowserLoadCompleted() также отменит регистрацию файла cookie, если он установлен.

+0

Прежде всего спасибо за всю информацию, ее указали несколько улучшений, которые я могу сделать. Для множественного вызова DisposeControls этот метод вызывается базовым классом по дереву, реализующему стандартный шаблон размещения.Для проблемы с навигацией это не вызывает беспокойства в этом приложении, URL-адрес загружается из файла конфигурации, и браузер никогда не будет перемещен. Наконец, для события полной загрузки, имеет ли смысл, чтобы обработчик событий отменил регистрацию? – Phaeze

+0

Также вы могли бы дать краткое объяснение того, откуда приходит 0x80004005, если я правильно понимаю, что код, данный для E_FAIL, просто хочу быть уверенным. – Phaeze

+0

Отмена регистрации обработчика событий не требуется. Конечно, 0x80004005 - это E_FAIL, всегда будет. –