У нас есть 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
}
}
}
Прежде всего спасибо за всю информацию, ее указали несколько улучшений, которые я могу сделать. Для множественного вызова DisposeControls этот метод вызывается базовым классом по дереву, реализующему стандартный шаблон размещения.Для проблемы с навигацией это не вызывает беспокойства в этом приложении, URL-адрес загружается из файла конфигурации, и браузер никогда не будет перемещен. Наконец, для события полной загрузки, имеет ли смысл, чтобы обработчик событий отменил регистрацию? – Phaeze
Также вы могли бы дать краткое объяснение того, откуда приходит 0x80004005, если я правильно понимаю, что код, данный для E_FAIL, просто хочу быть уверенным. – Phaeze
Отмена регистрации обработчика событий не требуется. Конечно, 0x80004005 - это E_FAIL, всегда будет. –