【问题标题】:Running Methods Simultaneously同时运行方法
【发布时间】:2015-10-07 04:13:50
【问题描述】:

我有一个 Dog 类,其中包含一个 Run 方法,它可以在屏幕上移动图片:

public bool Run()
{
    Point p = PictureBoxDog.Location;

    while(p.X < 530)
    {
        int movement = Randomizer.Next(0, 3);
        p.X += movement;
        PictureBoxDog.Location = p;
    }

    if (Location == 4) //Incomplete section.
        return true;
    else
        return false;
}

这个方法是从一个按钮点击事件中调用的,其中创建了 4 个狗对象,每个对象都调用 Run 方法:

private void button1_Click(object sender, EventArgs e)
{
    Dog dog1 = new Dog(pictureDog1);
    Dog dog2 = new Dog(pictureDog2);
    Dog dog3 = new Dog(pictureDog3);
    Dog dog4 = new Dog(pictureDog4);

    dog1.Run();
    dog2.Run();
    dog3.Run();
    dog4.Run();
}

问题是每个方法一个接一个地执行,而不是同时执行。我希望每种方法同时运行。如果我删除 while 语句,那么所有方法都会同时执行,但在 while 循环中,它们会一个接一个地执行。非常感谢有关如何解决此问题的任何建议。没有while循环的运行方法:

public bool Run() //Dog1.Run()
{
    Point p = PictureBoxDog.Location;

    int movement = Randomizer.Next(0, 30);
    //Location += movement;

    p.X += movement;
    PictureBoxDog.Location = p;

    if (Location == 4) //Incomplete code.
    return true;
    else
    return false;
}

【问题讨论】:

  • 添加一个计时器并从那里调用方法。这样您就不必弄乱线程,如果您不知道自己在做什么,这会导致 UI 元素出现问题。

标签: c# multithreading


【解决方案1】:

动画和 WinForms 通常并不简单。程序员通常做的是设置一个游戏循环。游戏循环会做三件事 - 获取用户输入,更新精灵的新位置,然后在屏幕上绘制精灵。

using System.Threading;

public partial class Form1
{ 
   private Timer _timer;
   private Dog _dog1, _dog2, _dog3, _dog4;

   public void InitializeComponent()
   {
      SetupDogs();

      // Every quarter of a second, run the function GameLoop
      _timer = new Timer(GameLoop, null, 
        TimeSpan.FromSeconds(0.25),
        TimeSpan.FromSeconds(0.25));
   }

   private void SetupDogs()
   {
      _dog1 = new Dog(PictureBoxDog1);
      _dog2 = new Dog(PictureBoxDog2);
      _dog3 = new Dog(PictureBoxDog3);
      _dog4 = new Dog(PictureBoxDog4);

   }

   public void GameLoop(object state)
   {
       GetUserInput();
       Update();
       Draw();
   }

   public void GetUserInput()
   {
     // You don't need this now. But if you need to
     // process user input later, you can do it here.
     //
     // e.g. if Last key 
     //   pressed  was arrow-left or 
     //   arrow-right etc.
   }

   public void Update()
   {
     _dog1.Update();
     _dog2.Update();
     _dog3.Update();
     _dog4.Update();
   }

   public void Draw()
   {
      // Draw on the main UI thread
      Dispatcher.BeginInvoke(() => 
      {
         _dog1.Draw();
         _dog2.Draw();
         _dog3.Draw();
         _dog4.Draw();
      });
   }

}

那么你的 Dog 类看起来像这样。每次计时器滴答时,它都需要Update它的位置,然后绘制它的位置:

public class Dog
{

  bool _isRunning = true;

  Point Location { get; set; }

  Point NextLocation { get; set; }

  PictureBox PictureBoxDog { get; set; }

  public Dog(PictureBox pictureBox) 
  {
     PictureBoxDog = pictureBox;

     Location = GetRandomLocation();

     NextLocation = GetRandomLocation();
  }

  private Point GetRandomLocation()
  {
     var random = new Random();
     return new Point(random.Next(800), random.Next(800));
  }

  public void Update()
  {
    // Calculates the new coordinates for a dog

    // The dog starts from a random position, and is 
    // given a new random position to run towards.

    // If the dog has arrived at the new random position, then
    // give the dog a new random position to run to again
    if (NextLocation.X == Location.X && NextLocation.Y == Location.Y)
    {
      NextLocation = GetRandomLocation();
    }

    if (_isRunning)
    {
       // Move the dog closer to its destination
       // dx and dy can be -1, 0, or 1
       var dx = Math.Sign(NextLocation.X - Location.X);
       var dy = Math.Sign(NextLocation.Y - Location.Y);

       Location = new Point(Location.X + dx, Location.Y + dy);
    }
  }

  public void Draw()
  {
     PictureBoxDog.Location = Location;
  }
}

【讨论】:

  • 喜欢这种方法,但我会重新使用Random 实例并在表单/图片框上使用BeginInvoke 调度绘图调用。
  • 感谢您的帮助,但这对我来说似乎太高级了。我真的无法理解它。我对 C# 不是很有经验。
  • 用C#写动画代码,有时不得不回首往事。您应该首先阅读并理解游戏循环的概念。这是一个很小但很重要的想法。在示例中,每次计时器滴答作响,您都会被问到狗在哪里?接下来,您将被要求在狗所在的位置绘制/定位图片框。
【解决方案2】:

试试这个

Dispatcher.BeginInvoke((Action) (() =>
{
     dog1.Run();
}));
Dispatcher.BeginInvoke((Action) (() =>
{
     dog2.Run();
}));
Dispatcher.BeginInvoke((Action) (() =>
{
     dog3.Run();
}));
Dispatcher.BeginInvoke((Action) (() =>
{
     dog4.Run();
}));

对于 While 循环,也可以使用这个

Dispatcher.BeginInvoke((Action) (() =>
{
    while(p.X < 530)
    {
        int movement = Randomizer.Next(0, 3);
        p.X += movement;
        PictureBoxDog.Location = p;
    }
}));

BeginInvoke 是异步的;因此,控制在调用对象后立即返回到调用对象。 BeginInvoke 返回一个 DispatcherOperation 对象,当委托在事件队列中时,该对象可用于与委托进行交互。 BeginInvoke 返回的 DispatcherOperation 对象可以通过多种方式与指定的委托进行交互

【讨论】:

  • 它说我需要一个对象引用才能使用 Dispatcher。我应该做些什么?抱歉,我对这些东西不是很有经验。
  • 问题涉及PictureBoxes,即WinForms。你说的是 Dispatcher,它是 WPF——我怀疑它与 WinForms 事件循环配合得很好。
  • @JoeWhite:根据 Martin Konicek You can use Dispatcher even in a WinForms app
  • @grammer:如果您使用的是 WinForms,那么您可以看看 convert wpf dispatcher to winformsuse Dispatcher.BeginInvoke properly
  • 这使得 Dispatcher 工作: Dispatcher d = Dispatcher.CurrentDispatcher; d.BeginInvoke((Action)(() => { dog1.Run(); }));
【解决方案3】:

为什么不试试这个?

Task.Factory.StartNew( () => Parallel.ForEach<Dog>(Dogs, dog=> dog.run()));

要正确使用此指令,您应该创建一个 Dog 列表。

 List<Dog> Dogs = new List<Dog>();
        Dogs.Add(dog1);
        Dogs.Add(dog2);
        Dogs.Add(dog3);
        Dogs.Add(dog4);
        Task.Factory.StartNew(() => Parallel.ForEach<Dog>(Dogs, dog => dog.Run()));

如果你不喜欢列表:

Task.Factory.StartNew(() => dog1.Run());
Task.Factory.StartNew(() => dog2.Run());
Task.Factory.StartNew(() => dog3.Run());
Task.Factory.StartNew(() => dog4.Run());

要从不同的线程与 UI 交互,您需要使用委托并调用 Control.Invoke/BeginInvoke。 您可以使用 InvokeRequired 属性测试是否需要调用 Invoke。 所以在你的情况下:

if (PictureBoxDog.InvokeRequired){
PictureBoxDog.Invoke((MethodInvoker)(() => PictureBoxDog.location = p));}

【讨论】:

  • 试过了。它导致另一条错误消息:附加信息:跨线程操作无效:控件'pictureDog4'从创建它的线程以外的线程访问
  • 当你对在其他线程中创建的控件执行跨线程操作时必须使用 if (control.InvokeRequired) { control.Invoke(handler); } 其中 handler 是 typeof Action
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-12
相关资源
最近更新 更多