Страницы

Поиск по вопросам

понедельник, 24 февраля 2020 г.

Исключение при попытке закрыть форму с браузером

#c_sharp #net #winforms #webbrowser


Есть форма с единственным контролом типа WebBrowser.
Для обработки нажатия Ecs используется переопределение ProcessCmdKey.
При загрузке формы выполняется открытие стараницы в webBrowser1.

Вот код:

public partial class Form1 : Form
{
  public Form1()
  {
    InitializeComponent();
  }

  protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
  {
    if (keyData == Keys.Escape)
    {
      this.Close();
      return true;
    }

    return base.ProcessCmdKey(ref msg, keyData);
  }

  private void Form1_Load(object sender, EventArgs e)
  {
    webBrowser1.Navigate("about:blank");
  }
}


Проблема в том, что при нажатии Esc форма закрывается, но приложение падает:


  RaceOnRCWCleanup occurred
  
  Message: Managed Debugging Assistant 'RaceOnRCWCleanup' has detected a problem
in 'C:\Тут был путь к проекту\bin\Release\Test - Close by Esc with WebBrowser.vshost.exe'.
  
  Additional information: Была предпринята попытка освободить RCW, находящийся в
использовании. RCW используется активным потоком или другим потоком. Попытка освободить
RCW, находящийся в использовании, может привести к повреждению или потере данных.


Стектрейс показывается такой:

    mscorlib.dll!System.__ComObject.FinalReleaseSelf()  Unknown
    mscorlib.dll!System.Runtime.InteropServices.Marshal.FinalReleaseComObject(object
o) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.WebBrowserBase.TransitionFromLoadedToPassive()
   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.WebBrowserBase.TransitionDownTo(System.Windows.Forms.WebBrowserHelper.AXState
state)  Unknown
    System.Windows.Forms.dll!System.Windows.Forms.WebBrowser.Dispose(bool disposing)
   Unknown
    System.dll!System.ComponentModel.Component.Dispose()    Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.Dispose(bool disposing)
  Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Form.Dispose(bool disposing)  Unknown
>   Test - Close by Esc with WebBrowser.exe!Test___Close_by_Esc_with_WebBrowser.Form1.Dispose(bool
disposing) Line 21   C#
    System.Windows.Forms.dll!System.Windows.Forms.Form.WmClose(ref System.Windows.Forms.Message
m)  Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Form.WndProc(ref System.Windows.Forms.Message
m)  Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref
System.Windows.Forms.Message m) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref
System.Windows.Forms.Message m)   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr
hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam)  Unknown
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    System.Windows.Forms.dll!System.Windows.Forms.Control.SendMessage(int msg, int
wparam, int lparam)  Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Form.Close()  Unknown
    Test - Close by Esc with WebBrowser.exe!Test___Close_by_Esc_with_WebBrowser.Form1.ProcessCmdKey(ref
System.Windows.Forms.Message msg, System.Windows.Forms.Keys keyData) Line 25    C#
    System.Windows.Forms.dll!System.Windows.Forms.Control.ProcessCmdKey(ref System.Windows.Forms.Message
msg, System.Windows.Forms.Keys keyData)    Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.PreProcessMessage(ref System.Windows.Forms.Message
msg)   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.WebBrowserBase.PreProcessMessage(ref
System.Windows.Forms.Message msg)    Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.PreProcessControlMessageInternal(System.Windows.Forms.Control
target, ref System.Windows.Forms.Message msg)   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.PreProcessControlMessage(ref
System.Windows.Forms.Message msg)    Unknown
    System.Windows.Forms.dll!System.Windows.Forms.WebBrowserSiteBase.System.Windows.Forms.UnsafeNativeMethods.IOleControlSite.TranslateAccelerator(ref
System.Windows.Forms.NativeMethods.MSG pMsg, int grfModifiers)   Unknown
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    System.Windows.Forms.dll!System.Windows.Forms.WebBrowserBase.PreProcessMessage(ref
System.Windows.Forms.Message msg)    Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Control.PreProcessControlMessageInternal(System.Windows.Forms.Control
target, ref System.Windows.Forms.Message msg)   Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.PreTranslateMessage(ref
System.Windows.Forms.NativeMethods.MSG msg) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.System.Windows.Forms.UnsafeNativeMethods.IMsoComponent.FPreTranslateMessage(ref
System.Windows.Forms.NativeMethods.MSG msg) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(System.IntPtr
dwComponentID, int reason, int pvLoopData)  Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(int
reason, System.Windows.Forms.ApplicationContext context)    Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.ThreadContext.RunMessageLoop(int
reason, System.Windows.Forms.ApplicationContext context) Unknown
    System.Windows.Forms.dll!System.Windows.Forms.Application.Run(System.Windows.Forms.Form
mainForm)   Unknown
    Test - Close by Esc with WebBrowser.exe!Test___Close_by_Esc_with_WebBrowser.Program.Main()
Line 20  C#
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence
assemblySecurity, string[] args) Unknown
    Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
  Unknown
    mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state)
   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
  Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
  Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext
executionContext, System.Threading.ContextCallback callback, object state) Unknown
    mscorlib.dll!System.Threading.ThreadHelper.ThreadStart()    Unknown


Что можно сделать?
    


Ответы

Ответ 1



Это не ошибка, это лишь предупреждение, выдаваемое Managed Debugging Assistant'ом (это такое расширение отладчика), которому кажется, что с вашим кодом что-то не так. Вы используете Runtime Callable Wrapper, специальную обёртку для COM-контролов (WebBrowser по сути является COM-контролом). Когда вы пытаетесь закрыть форму, расширение отладчика бросается на амбразуру и заявляет вам, что браузер ещё используется, а вы убиваете его до того, как его текущая функция полностью отработает. Такое может по идее привести к реальным проблемам лишь если ваш код многопоточный. Проблема воспроизводится в обычном коде при использовании CancelProperty у кнопки, или DialogResult. Или в вашем случае, когда у вас есть особая реакция на Esc. Фокус находится на WebBrowser'е, он получает нажатие Esc. Обвязка ActiveX собирается сообщить контейнеру о нажатии, чтобы он мог на него отреагировать. (Это нужно, например, для поддержки клавиатурных шорткатов, Tab'а, Enter/Escape для активизации кнопок и тому подобного.) Если при этом нажатие на клавишу закрывает форму (как в вашем случае), отладчик видит, что на WebBrowser'е вызывается Dispose в то время как код его RCW ещё выполняется. Опасность заключается в том, что когда код вернётся в RCW, он имеет право работать с COM-объектом (он же не знает, что у него из под носа этот самый объект увели), и произойдёт вылет — это не такой уж и редкий сценарий. Окей, в конкретно данном случае вылет вроде бы не воспроизводится так просто. Но по идее проблема может произойти если финализирующий поток доберётся до COM-объекта до того, как произойдёт возврат из обработчика Esc. Так что у нач возможна гонка между UI-потоком и потоком финализатора. В качестве заплатки можно отложить реальное закрытие на момент, когда код обвязки ActiveX закончит работу. Например, вызывая закрытие асинхронно: if (keyData == Keys.Escape) { this.BeginInvoke((Action)this.Close); return true; } Вольный перевод этого ответа с en.SO

Ответ 2



Возможный вариант решения - подписаться на PreviewKeyDown самого WebBrowserа: private void webBrowser1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) { this.Close(); } Обращаю внимание, что подписка на нажатие клавиши на форме не работает, независимо от значения свойства KeyPreview. Нужно именно на браузер.

Ответ 3



Базовый метод ProcessCmdKey надо вызывать до вызова Close. protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { var res = base.ProcessCmdKey(ref msg, keyData); if (keyData == Keys.Escape) { this.Close(); res = true; } return res; } Иначе, если base.ProcessCmdKey вызывается после закрытия формы, то надо удалять браузер перед закрытием. protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Escape) { this.Controls.Remove(wb); // ДОБАВИТЬ ЭТУ СТРОКУ this.Close(); return true; } return base.ProcessCmdKey(ref msg, keyData); } Проверил в Visual Studio Community 2015, C# Interactive // Microsoft (R) Roslyn C# Compiler version 1.1.0.51204 #r "System.WIndows.Forms" using System.Windows.Forms; public class Form1 : Form { WebBrowser wb; public Form1() { wb = new WebBrowser() { Dock = DockStyle.Fill, Parent = this }; wb.Navigating += (s, e) => Console.WriteLine("Navigating " + e.Url); wb.Navigated += (s, e) => Console.WriteLine("Navigated " + e.Url); wb.DocumentCompleted += delegate { Console.WriteLine("DocumentCompleted"); }; } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Escape) { this.Controls.Remove(wb); // ДОБАВИТЬ ЭТУ СТРОКУ this.Close(); return true; } return base.ProcessCmdKey(ref msg, keyData); } protected override void OnLoad(EventArgs e) { wb.Navigate("about:blank"); } } var f = new Form1(); f.Activated += delegate { SendKeys.Send("{ESC}"); }; f.ShowDialog(); Работает нормально, закрывается при нажатии Esc и выводит Navigating about:blank Navigated about:blank DocumentCompleted

Комментариев нет:

Отправить комментарий