Dispatcher仅从数组

时间:2017-04-26 11:54:44

标签: c# wpf multidimensional-array

我有一个Tile个对象的多维数组。

我的程序首先在随机位置选择Tile(例如(0,0)),访问随机邻居Tile,将其标记为已访问并移至下一个邻居,直到那里没有未见过的邻居。

这是我的代码:

while (HasUnvisitedNeighbours())
{
    Task.Run(() =>
    {
        Application.Current.Dispatcher.BeginInvoke(new Action(() =>
        {
            CurrentTile.Draw();
        }));
        Thread.Sleep(500);
    });

    //1. Choose randomly a neighbour
    List<char> Keys = new List<char>(CurrentTile.Neighbours.Keys);
    char randomKey = Keys[rnd.Next(Keys.Count)];

    CurrentTile = CurrentTile.Neighbours[randomKey];
    CurrentTile.HasBeenVisited = true;
}

我通过我的程序进行了调试,并且我已经确认:

CurrentTile = CurrentTile.Neighbours[randomKey];

正确地将每个循环更改为正确的邻居,并且当旁边没有未访问的邻居时,循环将停止。

但在:

Task.Run(() =>
{
    Application.Current.Dispatcher.BeginInvoke(new Action(() =>
    {
        CurrentTile.Draw();
    }));
    Thread.Sleep(500);
});

CurrentTile.Draw()似乎总是在调试器中具有相同的值(CurrentTile = CurrentTile.Neighbours[randomKey];的最后一个值。所以似乎调度程序等待整个循环完成,然后它绘制最后一个{{ 1}}对象。

我的目的是绘制当前Tile - &gt;移动到邻居 - &gt;将其标记为当前图块 - &gt;绘制当前图块 - &gt; ...

2 个答案:

答案 0 :(得分:3)

您的BeginInvoke正在安排(排队)由特定Dispatcher完成的某些“工作”,即通常是您的主要UI消息循环/处理线程。

当您处于“while循环”时,您已经在Dispatcher线程上处理/工作(但它是针对不同/之前的消息)。

(通常)Dispatcher无法处理队列中的下一个“dispatch”消息,直到你完成了你正在做的事情并且控制权返回Dispatcher ...然后Dispatcher可以选择接下来要处理的消息(可能是您的消息,也可能是某些消息,具体取决于优先级)。

(有关Windows消息循环/调度程序如何工作的更深入描述,请参阅:Does using dispatcher.Invoke make my thread safe?

这段代码将从Task开始,这将导致访问随机磁贴,直到您的所有HasUnvisitedNeighbours()都返回false。

您也可以使用.Invoke(同步版本)代替.BeginInvoke.Wait组合。

注意:通常说使用.BeginInvoke而不是.Invoke更可取,因为在某些情况下.Invoke可能会死锁....但我认为在这种情况下您可以使用任

如果您愿意,可以执行HasUnvisitedNeighbours();,然后选择.Draw,然后在一个“调用”中选择下一个图块。

然而,我把它分成2个“调用”,因为你可以重构它,这样你只得到一次未访问的瓦片列表(通过查询所有的瓦片.HasBeenVisited属性),然后只修改那个列表而不是询问所有的瓷砖再次 - 这是因为你是访问瓷砖的人...所以你知道他们现在是否已被访问过。

void VisitAllNeighbours()
{
    Task.Run(() =>
    {
        while (true)
        {
            bool bHasUnvisitedNeighbours = false;

            DispatcherOperation dispopunvisitedtiles = Application.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                bHasUnvisitedNeighbours = HasUnvisitedNeighbours();
            };

            dispopunvisitedtiles.Wait();

            if (!bHasUnvisitedNeighbours)
                break;

            DispatcherOperation dispopvisitnext = Application.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                CurrentTile.Draw();

                //1. Choose randomly a neighbour
                List<char> Keys = new List<char>(CurrentTile.Neighbours.Keys);
                randomKey = Keys[rnd.Next(Keys.Count)];

                CurrentTile = CurrentTile.Neighbours[randomKey];
                CurrentTile.HasBeenVisited = true;

            }));

            // Take out this "wait" if you want multiple "visits" to be
            // pending/scheduled instead of only one at a time - be
            // careful that your "random visiting" selection code
            // doesn't cause too many "visits" to be outstanding though!
            //
            // Note: each visit never occurs at the same time as another
            // one as the "Dispatcher"/message loop is providing the
            // serialization.

            dispopvisitnext.Wait();

            Thread.Sleep(500); // if you want a "delay" between each Tile visit
        );
    }
}

答案 1 :(得分:0)

  

所以似乎调度员等待整个循环完成,然后它绘制最后的Tile对象。

是的,Dispatcher.BeginInvoke计划最终在调度程序线程上执行的委托。但是,为什么你只是打电话给Dispatcher.BeginInvoke开始一项新任务?这没有多大意义。

您可以使用同步Invoke方法等待调度程序线程上的方法被激活:

while (HasUnvisitedNeighbours())
{
    Application.Current.Dispatcher.Invoke(new Action(() => CurrentTile.Draw()));
    Thread.Sleep(500);

    List<char> Keys = new List<char>(CurrentTile.Neighbours.Keys);
    char randomKey = Keys[rnd.Next(Keys.Count)];

    CurrentTile = CurrentTile.Neighbours[randomKey];
    CurrentTile.HasBeenVisited = true;
}

修改

UI线程不能同时执行循环并更新UI。您可以尝试在后台线程上运行代码:

private void Method()
{
    Task.Run(async () =>
    {
        while (HasUnvisitedNeighbours())
        {
            await Application.Current.Dispatcher.BeginInvoke(new Action(() => CurrentTile.Draw()));
            await Task.Delay(500);

            List<char> Keys = new List<char>(CurrentTile.Neighbours.Keys);
            char randomKey = Keys[rnd.Next(Keys.Count)];

            CurrentTile = CurrentTile.Neighbours[randomKey];
            CurrentTile.HasBeenVisited = true;
        }
    });
}