gpt4 book ai didi

c# - WebBrowser 控件幻灯片在长时间运行后挂起

转载 作者:太空宇宙 更新时间:2023-11-03 15:37:07 25 4
gpt4 key购买 nike

我是一个相当新的开发者,这个让我难住了。

我的 WinForms 应用程序是网站的幻灯片放映,它在 URL 列表中旋转,通过使用第二个表单作为“窗帘”在每次转换时淡入/淡出。它本应无限期运行,但在运行几天后一直卡在转换上。

表格 1:

HttpWebResponse response = null;
List<Slide.Doc> sList = null;

bool repeatSlideshow = true;
bool pageLoaded = false;

double curtainAnimStep = 0.05;
int errorCount = 0;

public Form1()
{
InitializeComponent();

CursorShown = false;

this.Visible = true;
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Maximized;

webBrowser1.ScrollBarsEnabled = false;
webBrowser1.ScriptErrorsSuppressed = true;

Slideshow(environment, channel);
}


public void Slideshow(string environment, string channel)
{
while (repeatSlideshow)
{
try
{
sList = Slide.convertJSONToSlide(Slide.getParams(environment, channel));
}
catch (Exception)
{
Form2 curtain = new Form2(curtainAnimStep);
curtain.Show();
waitForFade(curtain, 1);
displayError();
raiseCurtain(curtain, curtainAnimStep);
waitForFade(curtain, 0);
curtain.Dispose();
waitAround(30);
continue;
}

foreach (Slide.Doc s in sList)
{
bool slideWasDisplayed = false;

Form2 curtain = new Form2(curtainAnimStep);
curtain.Show();
waitForFade(curtain, 1);
slideWasDisplayed = displaySlide(s.URL_TEXT);
if (slideWasDisplayed == false)
{
webBrowser1.DocumentText = "<html><body style='background-color: #1C1C1C;'></body></html>";
redrawPage();
}
raiseCurtain(curtain, curtainAnimStep);
waitForFade(curtain, 0);
curtain.Dispose();
if (slideWasDisplayed == true)
{
waitAround(s.DISPLAY_SEC);
}

}

if (errorCount == sList.Count)
{
Form2 curtain = new Form2(curtainAnimStep);
curtain.Show();
waitForFade(curtain, 1);
displayError();
raiseCurtain(curtain, curtainAnimStep);
waitForFade(curtain, 0);
curtain.Dispose();
waitAround(30);
}

errorCount = 0;

Utilities.Web.WebBrowserHelper.WebBrowserHelper.ClearCache();
}
}

public bool displaySlide(string slideUrl)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(slideUrl);
request.Timeout = 1000;
try
{
response = (HttpWebResponse)request.GetResponse();
webBrowser1.Navigate(slideUrl);
redrawPage();
response.Dispose();
return true;
}
catch (WebException)
{
errorCount++;
return false;
}
}

public void redrawPage()
{
while (pageLoaded == false)
{
Application.DoEvents();
}
webBrowser1.Invalidate();
Application.DoEvents();
pageLoaded = false;
}

public void raiseCurtain(Form curtain, double curtainAnimStep)
{
while (curtain.Opacity > 0)
{
curtain.Opacity -= curtainAnimStep;
Application.DoEvents();
System.Threading.Thread.Sleep(10); // How long between shifts in opacity (NOT interval between slides)
}
}

public void waitAround(int duration)
{
DateTime dt2 = DateTime.Now;
while (dt2.AddSeconds(duration) > DateTime.Now)
{
Application.DoEvents();
}
}

public void waitForFade(Form curtain, int finalOpacity)
{
while (curtain.Opacity != finalOpacity)
{
DateTime dt = DateTime.Now;
dt = dt.AddSeconds(1);
while (dt > DateTime.Now)
{
Application.DoEvents();
}
}
}

private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
pageLoaded = true;
}

表格 2:

public Form2(double animStep)
{
InitializeComponent();
this.AnimStep = animStep;
}

public double AnimStep { get; set; }

private async void Form2_Load(object sender, EventArgs e)
{
while (Opacity < 1.0)
{
await Task.Delay(10);
Opacity += AnimStep;
}
Opacity = 1;
}

我已经为此工作了很长时间,但我不得不承认我真的不知道此时我应该寻找什么。

Application.DoEvents 的使用是否负责?将它们排除在外会破坏应用程序,但我想不出替代方法。

最佳答案

查看您的代码(如 Noseratio 所示),我建议的一件事是摆脱对 DoEvents 调用的需要。请记住,在 Windows 中有一个专用的 UI 线程用于更新窗体上的控件。当您在同一个 UI 线程上做很多事情(在循环中,调用一堆方法)时,Windows 控件依赖于您与它们共享一些时间的合作,因此调用 DoEvents

我要使用 BackgroundWorker和一个 TimerWaitHandle安排将从后台线程更新 UI 的命令。这样我们就可以在 UI 线程上做尽可能少的事情。

表单加载

Form1 将只有一个 webbrowsercontrol 和一个 backgroundworker。队列将保存需要执行的命令。我们从 Load 事件启动 Backgroundworker。

    Form2 frm2 = new Form2();
Queue<ICommandExecutor> commands = new Queue<ICommandExecutor>();

private void Form1_Load(object sender, EventArgs e)
{
frm2.Show();
frm2.BringToFront();
commands.Enqueue(new LoadSlideShow(this, frm2, commands));
backgroundWorker1.RunWorkerAsync();
}

后台 worker

Backgroundworker DoWork 事件是在其自己的后台线程上运行的引擎。只要在队列中找到命令,它就会运行。获取命令后,它的 Execute 方法被触发。如果命令支持处置,则调用 Dispose 方法并处理命令,然后我们重新开始。

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while(commands.Count>0)
{
ICommandExecutor cmd = commands.Dequeue();
try
{
cmd.Execute();
// dispose if we can
IDisposable sync = cmd as IDisposable;
if (sync != null)
{
sync.Dispose();
}
}
catch(Exception exp)
{
// add commands here
Trace.WriteLine("error" + exp.Message);
}
}
}

命令

有一个标准接口(interface)可用于实现命令模式。 ICommandExecutor有一个方法,Execute。我们可以创建不同的类来实现这个接口(interface)。每个类都有自己的状态和引用,它可以像计时器一样简单,也可以像加载要显示的新一批 url 一样复杂。

    public class ShowSlide:ICommandExecutor
{
string url;
Form1 form;
AutoResetEvent done = new AutoResetEvent(false);
public ShowSlide(Form1 form, string url)
{
this.url = url;
this.form = form;
}

public void Execute()
{
// if we are not on the UI thread...
if (form.InvokeRequired)
{
// ... switch to it...
form.Invoke(new MethodInvoker(Execute));
}
else
{
// .. we are on the UI thread now
// reused from your code
form.displaySlide(url);
}
}
}

这是一个计时器。请注意如何使用 Timer 类和 timerDone 等待句柄,只有在调用 Dispose 时计时器已完成时,后台线程才能继续工作。

    public class WaitForSeconds: ICommandExecutor, IDisposable
{
int ms;
System.Threading.Timer timer;
ManualResetEvent timerDone = new ManualResetEvent(false);
public WaitForSeconds(int secs)
{
this.ms = secs * 1000;
}

public void Execute()
{
// use a timer
timer = new System.Threading.Timer(
(state) => timerDone.Set() // signal we are done
);
timerDone.Reset();
timer.Change(this.ms, Timeout.Infinite);
}

public void Dispose()
{
timerDone.WaitOne();
timerDone.Dispose();
timer.Dispose();
}
}

为了以正确的顺序设置命令,我们使用以下命令类实现,它将命令队列、Form1 和 Form2 作为其构造函数的参数。 Execute 命令加载所有要提供给 webbrowser 控件的 url。对于每个 url,它将需要执行的命令添加到队列中。最后,this 实例也被添加到队列中,这意味着如果所有命令都已处理,将再次使用该类。队列永远不会空着。

    public class LoadSlideShow: ICommandExecutor
{
readonly Queue<ICommandExecutor> commands;
readonly Form1 form;
readonly Form2 form2;
public LoadSlideShow(Form1 form, Form2 form2, Queue<ICommandExecutor> cmds)
{
this.form = form;
commands = cmds;
this.form2 = form2;
}

public void Execute()
{
var list = Slide.convertJSONToSlide(null);
foreach (var slide in list)
{
commands.Enqueue(new ShowSlide(form, slide.URL_TEXT));
commands.Enqueue(new WaitForSeconds(1));
//commands.Enqueue(new LowerCurtain(form2));
commands.Enqueue(new WaitForSeconds(slide.DISPLAY_SEC));
//commands.Enqueue(new RaiseCurtain(form2));
}
commands.Enqueue(this);
}
}

这基本上就是让基本幻灯片放映所需的全部内容。

对于所谓的窗帘,我们将对 Form2 做一些类似的事情,但我也会使用 BackgroundWorker_progress 事件。

Form2 窗帘

Form2 将通过循环更改它的 Opacity 来充当窗帘。它有自己的后台 worker :

    ManualResetEvent stateChange = new ManualResetEvent(false);
public ManualResetEvent stateChangeDone = new ManualResetEvent(false);

private void Form2_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while(stateChange.WaitOne())
{
stateChange.Reset();
var progressDone = new AutoResetEvent(false);
int progress = 0;
using(var timer = new System.Threading.Timer(_=>
{
backgroundWorker1.ReportProgress(progress);
progress += 2;
if (progress>=100)
{
progressDone.Set();
}
}, null, 0, 25))
{
progressDone.WaitOne();
}
stateChangeDone.Set();
}
}

后台工作人员调用 ResportProgress 并使用一个指示其 prpgress 的 int。这会引发 ProgressChanged 事件。根据 Curtain 需要处于的状态,我们计算出正确的 Opacity 值。

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
switch(state)
{
case Curtain.Up:
this.Opacity = e.ProgressPercentage / 100.0;
break;
case Curtain.Down:
this.Opacity = (100 - e.ProgressPercentage) / 100.0;
break;
}
}

为了开始这一切,我们创建了两个名为 Up 和 Down 的公共(public)方法:

    enum Curtain
{
Up,
Down
}

Curtain state;

public void Up()
{
state = Curtain.Up;
stateChange.Set();
stateChangeDone.Reset();
}

public void Down()
{
state = Curtain.Down;
stateChange.Set();
stateChangeDone.Reset();
}

这样我们只剩下命令类的实现,这些命令类将被添加到命令队列并由 Form1 的后台 worker 处理:

    public class RaiseCurtain:ICommandExecutor, IDisposable
{
readonly Form2 form2;

public RaiseCurtain( Form2 form2)
{
this.form2 = form2;
}

public void Execute()
{
if (form2.InvokeRequired)
{
form2.Invoke(new MethodInvoker(Execute));
}
else
{
form2.BringToFront();
form2.Up();
}
}

public void Dispose()
{
form2.stateChangeDone.WaitOne();
}
}

public class LowerCurtain : ICommandExecutor,IDisposable
{
readonly Form2 form2;

public LowerCurtain(Form2 form2)
{
this.form2 = form2;
}

public void Execute()
{
if (form2.InvokeRequired)
{
form2.Invoke(new MethodInvoker(Execute));
}
else
{
form2.Down();
}
}

public void Dispose()
{
form2.stateChangeDone.WaitOne();
}
}

就是这样。我们已经消除了对 DoEvents 的使用。

有一个警告:这并不能保证应用程序会在几个小时/几天后再次停止。原因是 possible memory-leak in the webbrowser control在我的测试中,我确实看到了相同的效果,私有(private)内存消耗缓慢但稳定地增加,而托管内存字节几乎保持不变。

由于没有一个帖子提供明确的答案,一个选项可能是将您的应用重新启动为 indicates in one of the answers here .从好的方面来说,您现在可以将其实现为命令类...

关于c# - WebBrowser 控件幻灯片在长时间运行后挂起,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31485607/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com