尝试读取或写入受保护的内存。这通常是其他内存已损坏的迹象。

8

我真的不明白这个代码为什么会出错。请您自己检查一下代码。

    void dispatcherTimer_Tick(object sender, EventArgs e)
{
    string srUrl = lstLocalIndex[irLocalIndex] + lstMainIndex[irMainIndex].Replace("0;","");

    Task.Factory.StartNew(() =>
    {
        startNewWindow(srUrl);
    });

}


    void startNewWindow(string srUrl)
{
    NewWindowThread<TitleWindow, string>(c => new TitleWindow(c), srUrl);
}

现在错误发生的地方是这段代码。我也会附上截图。

        private void NewWindowThread<T, P>(Func<P, T> constructor, P param) where T : Window
    {
        Thread thread = new Thread(() =>
        {
            T w = constructor(param);
            w.Show();
            w.Closed += (sender, e) => w.Dispatcher.InvokeShutdown();
            try
            {
                System.Windows.Threading.Dispatcher.Run();
            }
            catch
            {

            }
        });
        thread.SetApartmentState(ApartmentState.STA);
        try
        {
            thread.Start();
        }
        catch
        {

        }
    }

尽管我在新线程中调用它们,但此错误会导致整个软件抛出错误并停止工作 :(

这行代码抛出错误:System.Windows.Threading.Dispatcher.Run();

请查看屏幕截图:

enter image description here

C# 4.0 WPF


你实际上使用了两个线程,一个在Task中,另一个是Thread,最好将要运行的代码作为启动代码放置在Thread中。 - casperOne
@casperOne 我也尝试过那个方法,但应用程序仍然崩溃。而且这种情况不是立即发生的,而是在一段时间后才会出现。它可以运行30分钟左右,然后崩溃。崩溃时间是不固定的。 - Furkan Gözükara
不,它们说你的内存已经损坏了。一定是这样!(顺便说一句,这是我遇到过的最愚蠢的错误提示之一,除了“未指定的错误”)。 - leppie
3个回答

2

我一直在与客户解决这个问题,下面是我的发现。

我们正在开发一个WPF应用程序,该应用程序进行了大量的线程和后台处理。突然出现了这个异常,我开始进行了一些调查。经过大约一个小时的调查,我终于找到了罪魁祸首:

        var worker = new BackgroundWorker();
        worker.DoWork += (o, ea) => Dispatcher.BeginInvoke(new Action(() =>
        {
            //do some heavy processing here, plus UI work, then call another method.

            //inside that other method, I found this:
            var thread = new Thread(() =>
            {
                //do some heavy processing.
            }) { IsBackground = true };
            thread.Start();
        }));

似乎发生的情况是后台工作程序完成了它的工作并从执行中返回。然而,在该后台工作程序内部创建的线程仍在处理,并返回,只发现它所创建的线程已经超出范围,因此导致了AccessViolationException异常。
为了调试这个问题,我建议仔细关注异常发生的位置,并仔细检查您的调用堆栈,这取决于您是否在抛出异常时处于线程内部,调用堆栈可能已被破坏或丢失。

1

您正在使用lambda作为线程函数。此lambda将在新线程上调用。实际创建线程时,它将查找您提供的参数,即本地变量srUrl,但是在此发生时,您的函数(dispatcherTimer_Tick)已经退出,因此srUrl将位于不再正确定义的堆栈部分(因此出现访问冲突)。简单的解决方法是在类中定义一个变量并快速将srLoc放入其中。更合适的解决方案是实际将srLoc作为参数传递:

() =>
{
    startNewWindow(srUrl);
}

变成

(Action<string>){x => {startNewWindow(x);},
            new object[] {srUrl}

现在函数引用和字符串的适当副本已保存用于函数调用,即使原始srUrl在线程启动时超出范围也没有关系。我不确定任务工厂是否允许传递参数数组。调度程序通常有一个重载来处理这个问题,所以也许你想让你的窗口来处理这个问题。

现在你实际上需要多次执行此操作,因此每次传递参数时可能需要包装它们。


1
谢谢你的回答。实际上,我通过编写另一个应用程序来读取文件中的URL解决了我的问题。所以现在我启动exe而不是新窗口。有时这些新的exe会出错,但软件仍然可以继续运行 :) 但是你的回答真的很专业,我很喜欢。 - Furkan Gözükara
1
@MonsterMMORPG 这个答案听起来很合理,但它是错误的。闭包将捕获局部变量(称为自由变量)。请参见这里:http://www.codethinked.com/c-closures-explained - ChrisWue
如果这是真的,那么你的代码甚至在显示窗口之前就会崩溃。在显示窗口后,srUrl 没有被使用。 - surfen
@ChrisWue:感谢提供链接。当我需要将来自另一个线程的调用重定向到UI线程(使用Dispatcher.BeginInvoke)时,我会使用这个。如果闭包可以正常工作,那么就不需要进行包装,但是如果不这样做,我会得到与此处提到的内存访问冲突完全相同的错误。因此,也许变量应该被捕获,但实际上并没有被捕获。 - Nzc

1

我之前也遇到过类似的问题。

错误发生是因为您的窗口超出了范围,垃圾回收器将其销毁。

使用 ShowDialog() 应该可以解决这个问题。请注意,这样做不会阻塞其他线程,因为窗口只会在调用线程中是模态的。

private void NewWindowThread<T, P>(Func<P, T> constructor, P param) where T : Window
{
    Thread thread = new Thread(() =>
    {
        System.Windows.Threading.Dispatcher.Run();
        T w = constructor(param);
        w.ShowDialog();
        w.Dispatcher.InvokeShutdown();
    });
    thread.SetApartmentState(ApartmentState.STA);
    try
    {
        thread.Start();
    }
    catch
    {
        // log&handle exceptions
    }
}

如果为真...您可以通过在加载事件中设置一个事件并在退出委托之前等待该事件来执行类似的操作。 - Yaur
这很有道理,但它不会阻止新创建窗口的用户界面吗? - surfen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接