Есть программа, которая создает несколько (2-8) Awesomium.Windows.Forms.WebControl в Form'е (используется Awesomium 1.7.5.1)
Необходимо постоянно получать значение InnerHTML каждого WebControl, перезагружать страницу webControl.Source = "https://someurl.com/"
, а также осуществлять ввод в поля <input>
и нажимать на кнопки element.click()
.
Есть две проблемы, при которых WebControl зависает и с которыми я не могу разобраться:
Спустя некоторое время работы программы метод webControl.ExecuteJavascriptWithResult(script)
возвращает значение result.IsUndefined
и webControl.GetLastError()
выдает значение Awesomium.Core.Error.TimedOut
. Я пробовал менять значение
webControl.SynchronousMessageTimeout
(сделал его = 10000
мс), но баг остался. Причем несколько раз после этой ошибки работа продолжается исправно, но в очередной раз webControl
зависает, остальные webControl
продолжают исправно работать еще в течении нескольких минут, затем зависает вся форма, в которой находятся эти webControl
.
Если используемый сайт большой (не могу указать размеры, просто заметил, что, если имеется 10-20 записей некоторого списка на сайте, то все работает, если 50-100, то появляется эта проблема), зависает на методе browser.Source = new Uri(url)
, причем события LoadingFrameComplete
и DocumentReady
отрабатывают, но WebControl
остается заблокированным (вызов перезагрузки страницы происходит из другого потока по событию таймера, в котором ставится webControl.CmdComplete = false
, а при завершении работы webControl.NavigateAsync(url)
возвращается обратно webBrowser.CmdComplete = true
). При последующих проходах таймера webBrowser.CmdComplete == false
долгое время (от нескольких секунд до десятков минут, хотя визуально страница давно загрузилась, работает и события загрузки отработали). И такое происходит только с несколькими webControl, необязательно со всеми. Обычно из четырех webControl
один-два работают, остальные виснут (в самом начале). Через какое-то время зависание проходит, затем снова появляется (возможно уже на изначально работающих webControl
).
Изначально программа написана с использованием движка Windows.Forms.WebBrowser
и она отлично работает без багов. Но появилась необходимость использовать авторизацию на одном и том же сайте одновременно разных пользователей. Для этих целей решил переписать ее под Awesomium. Поэтому проблема скорее всего в особенностях Awesomium
, использовании методов навигации и/или исполнения javascript'a, а не в алгоритмах самой программы.
Сразу скажу, что использование браузерного движка необходимо, поэтому предлагать WebRequest
не нужно.
webControl
создается в классе-обертке AwesomiumWebBrowserControl
. Методы класса-обертки:
//...
public AwesomeWebBrowserControl(string id)
{
//...
// инициализайция WebControl browser
// для каждого WebControl используется отдельная сессия и куки
string sess_path = System.Windows.Forms.Application.StartupPath + "\\sessions";
string acc_path = "\\" + id;
if (!System.IO.Directory.Exists(sess_path + acc_path)) System.IO.Directory.CreateDirectory(sess_path + acc_path);
Awesomium.Core.WebPreferences wp = Awesomium.Core.WebPreferences.Default;
wp.EnableGPUAcceleration = true;
Awesomium.Core.WebSession session = Awesomium.Core.WebCore.CreateWebSession(sess_path + acc_path, wp);
browser = new WebControl()
{
WebSession = session,
SynchronousMessageTimeout = 10000,
Name = id
//...
};
//...
}
//...
public Awesomium.Core.JSValue ExecuteJavascriptWithResult(string script)
{
const string METHOD_NAME = "ExecuteJavascriptWithResult";
try
{
if (IsReady()) return browser.ExecuteJavascriptWithResult(script);
}
catch (Exception ex)
{
Program.CalcException(CLASS_NAME + "." + METHOD_NAME, ex);
}
return Awesomium.Core.JSValue.Null;
}
public string Html()
{
const string METHOD_NAME = "Html";
try
{
var html_nodes = (Awesomium.Core.JSObject)ExecuteJavascriptWithResult("document.getElementsByTagName('html')");
if (html_nodes["length"] != 1) return null;
var html_node = (Awesomium.Core.JSObject)html_nodes.Invoke("item", 0);
HtmlRAM = html_node["innerHTML"]; // string HtmlRAM - определено в AwesomeWebBrowserControl
return HtmlRAM;
}
catch (Exception ex)
{
Program.CalcException(CLASS_NAME + "." + METHOD_NAME, ex);
}
return null;
}
public Awesomium.Core.JSValue Document()
{
const string METHOD_NAME = "Document";
try
{
return ExecuteJavascriptWithResult("document");
}
catch (Exception ex)
{
Program.CalcException(CLASS_NAME + "." + METHOD_NAME, ex);
}
return null;
}
//...
bool loading_frame_complete = true;
bool document_ready = true;
bool acquire_window = true;
public bool IsReady()
{
if (loading_frame_complete &&
document_ready &&
browser.IsDocumentReady &&
browser.IsLive &&
!browser.IsLoading &&
!browser.IsNavigating &&
!browser.IsCrashed &&
!browser.IsDisposed)
{
Awesomium.Core.JSValue jv_window = AcquireWindow();
if (jv_window.IsUndefined) acquire_window = false;
else acquire_window = true;
return acquire_window;
}
else return false;
}
public async Task NavigateAsync(string url)
{
TaskCompletionSource<bool> tcsNavigation = null;
TaskCompletionSource<bool> tcsReady = null;
browser.LoadingFrameComplete += (s, e) =>
{
if (tcsNavigation.Task.IsCompleted)
return;
if (e.IsMainFrame)
{
loading_frame_complete = true;
tcsNavigation.SetResult(true);
}
};
browser.DocumentReady += (s, e) =>
{
if (tcsReady.Task.IsCompleted)
return;
document_ready = true;
tcsReady.SetResult(true);
};
tcsNavigation = new TaskCompletionSource<bool>();
tcsReady = new TaskCompletionSource<bool>();
loading_frame_complete = false;
document_ready = false;
browser.Source = new Uri(url);
await tcsNavigation.Task;
await tcsReady.Task;
}
private Awesomium.Core.JSValue AcquireWindow()
{
// взято с http://answers.awesomium.com/questions/4784/workaround-for-executejavascriptwithresult.html
// на ход работы никак не влияет, используется для проверки ошибок
System.Diagnostics.Stopwatch methodStopWatch = System.Diagnostics.Stopwatch.StartNew();
Awesomium.Core.JSValue windowValue = this.browser.ExecuteJavascriptWithResult("window");
while (windowValue.IsUndefined && (methodStopWatch.ElapsedMilliseconds < 3000))
{
Program.WriteLine("Failed to acquire Javascript global window object. Probably a bug in the Awesomium framwork. Try again for a while");
Awesomium.Core.Error lastError = browser.GetLastError();
if (lastError != Awesomium.Core.Error.None)
{
switch (lastError)
{
case Awesomium.Core.Error.TimedOut:
Program.WriteLine("Cause of failure was TimedOut");
break;
case Awesomium.Core.Error.ConnectionGone:
Program.WriteLine("Cause of failure was ConnectionGone");
break;
case Awesomium.Core.Error.WebViewGone:
Program.WriteLine("Cause of failure was WebViewGone");
break;
case Awesomium.Core.Error.ObjectGone:
Program.WriteLine("Cause of failure was ObjectGone, that is object no longer exiet?");
break;
case Awesomium.Core.Error.BadParameters:
Program.WriteLine("Cause of failure was BadParameters?");
break;
case Awesomium.Core.Error.Generic:
Program.WriteLine("Cause of failure was Generic, we don't know what happened");
break;
default:
Program.WriteLine("Cause of failure was unknown error code");
break;
}
}
windowValue = this.browser.ExecuteJavascriptWithResult("window");
}
if (windowValue.IsUndefined)
{
Program.WriteLine("Map has not been properly loaded. Request for interaction with map rejected to avoid crash in Windows Forms");
}
return windowValue;
}
//...
вызов перезагрузки страницы и исполнения скриптов осуществляется из обработчика таймера потока Form
с WebControl
'ами. Ниже показаны типовые примеры использования навигации и исполнения javascript
:
await whc.NavigateAsync(url);
public void FocusIntern(bool must_focus = false)
{
const string MethodName = "FocusIntern";
if ((AutoFocus && (DateTime.Now - lastFocus).TotalSeconds >= secFocus) || must_focus)
{
using (dynamic wdoc = Document())
{
if (wdoc != null) wdoc.focus();
}
lastFocus = DateTime.Now;
}
}
[HandleProcessCorruptedStateExceptions]
public static async Task<RetVal<bool>> AuthInternAsync(AwesomeWebBrowserControl whc, string[] auth_data)
{
const string MethodName = "AuthInternAsync";
try
{
using ( dynamic result = (JSObject)whc.ExecuteJavascriptWithResult(Js.Auth(auth_data[0], auth_data[1])))
{
if (result.error) return RetVal<bool>.ErrorMessage(Name + "." + MethodName + ": <" + result.message + ">");
}
return new RetVal<bool>(true);
}
catch (Exception e)
{
Program.CalcException(Name + "." + MethodName, e);
}
return RetVal<bool>.ErrorMessage(Name + "." + MethodName + ": <Exception>");
}
// из класса Js
public static string Auth(string name, string password)
{
// метод генерации скрипта авторизации
const string MethodName = "Auth";
return @"(
function() {
var res = {};
res.error = true;
res.message = 'undefined';
try {
// здесь javascript код авторизации на сайте
// если авторизация удалась
res.error = false;
// иначе
{
res.error = true;
res.message = 'some message';
}
}
catch (e) {
res.message = '" + ClassName + "." + MethodName + @": <' + e.name + ':' + e.message + '>';
}
return res;
})();";
}
есть и другие методы (LogoutInternAsync
и пр.), но они все построены по типу AuthInternAsync
.
Вывод перед зависанием формы:
Любые предположения и предложения буду проверять и комментировать, я очень заинтересован в скорейшем решении своего вопроса.
Если нужно больше кода, пишите - добавлю.
Как всегда, сам пошутил - сам посмеялся. Конечно решение не очень красивое, но зато теперь все работает. К сути
Причина зависания:
Поток awesomium_process (поток движка awesomium) потребляет все больше и больше памяти (причем сам движок винить не стоит, на некоторых сайтах все работает хорошо, в то время как другие съедают какое то непомерное количество памяти (1.5гб за 10 минут)). Т.к. движок написан под x86, при достижении предела адресации awesomium_process крашится (но не крашится сама форма приложения), в следствии чего наблюдается зависание без каких-либо явных ошибок.
Решение (костыль):
Пишется простенькая программа - лаунчер, в которой запускаются приложения-браузеры. В лаунчере нужно поставить флаг получения событий от процесса и подписаться на событие Exit:
Process.EnableRaisingEvents = true;
Process.Exited += Process_Exited;
И, если событие происходит, перезапускать приложение-браузер. В самом приложении-браузере я сделал что-то типа watchdog-таймера, в котором проверяется работоспособность браузера. И если в течении, например, 10 секунд таймер не был сброшен - приложение вырубает само себя.
Если кто-то столкнется с такой же проблемой: конечно лаунчер работает чуть сложнее, если по каким-либо причинам в некоторые промежутки времени приложение нельзя просто взять и вырубить (происходит какое-то атомарное действие), то нужно как то ему (браузер-приложению) сообщить, чтобы оно выключилось само, как только это можно будет сделать без логических ошибок. Я использовал простенький WCF-интерфейс:
[ServiceContract]
public interface ICloser
{
[OperationContract]
void Shutdown();
}
Теперь вся эта система теперь работает 24/7 без ошибок.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Делаю в Unity магнит - через PhysicsOverlap находим, ищем rigidbody и притягиваем к себеКод
В форме у меня есть следующий код