在Task.Run里面调用,如何解决死锁问题?

时间:2017-05-23 15:32:02

标签: c# wpf multithreading async-await

我有一个静态方法,可以从任何地方调用。在执行期间,它将遇到Invoke。显然,当从UI线程调用此方法时,它将会死锁。

这是一个复制品:

public static string Test(string text)
{
    return Task.Run(() =>
    {
        App.Current.Dispatcher.Invoke(() => { } );
        return text + text;
    }).Result;
}
void Button_Click(object sender, RoutedEventArgs e) => Test();

我已经阅读了多个问题,并且喜欢@StephenCleary的10个答案(甚至还有一些与之相关的博客),但我无法理解如何实现以下目标:

  • 有一个静态方法,很容易调用并从任何地方获取结果(例如UI事件处理程序,任务);
  • 此方法应阻止调用者,之后调用者代码应继续在相同的上下文中运行;
  • 此方法不应冻结UI。

Test()的行为最相似的是MessageBox.Show()

是否可以实现?

P.S。:为了保持问题简短,我没有附加我的各种async/await尝试以及一个用于UI调用的尝试,但使用DoEvents一个看起来很糟糕。

2 个答案:

答案 0 :(得分:1)

你不能。

即使这3个要求中只有2个不能同时实现 - "这种方法应该阻止呼叫者"与#34冲突;这种方法不应该冻结UI"。

您必须以某种方式使此方法异步(await,回调)或使其在小块中可执行以仅在短时间内阻止UI,例如使用计时器来安排每个步骤。

重申你已经知道的内容 - 你可以阻止线程并在许多问题(如await works but calling task.Result hangs/deadlocks中讨论的同时回拨它。

答案 1 :(得分:0)

要实现MessageBox所做的事情(但没有创建窗口),可以这样做:

public class Data
{
    public object Lock { get; } = new object();
    public bool IsFinished { get; set; }
}

public static bool Test(string text)
{
    var data = new Data();
    Task.Run(() =>
    {
        Thread.Sleep(1000); // simulate work
        App.Current.Dispatcher.Invoke(() => { });
        lock (data.Lock)
        {
            data.IsFinished = true;
            Monitor.Pulse(data.Lock); // wake up
        }
    });
    if (App.Current.Dispatcher.CheckAccess())
        while (!data.IsFinished)
            DoEvents();
    else
        lock (data.Lock)
            Monitor.Wait(data.Lock);
    return false;
}

static void DoEvents() // for wpf
{
    var frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new Func<object, object>(o =>
    {
        ((DispatcherFrame)o).Continue = false;
        return null;
    }), frame);
    Dispatcher.PushFrame(frame);
}

这个想法很简单:检查当前线程是否需要调用(UI线程),然后运行DoEvents循环或阻塞线程。

可以从UI线程或其他任务调用

Test()

它有效(虽然没有经过充分测试),但它很糟糕。我希望这会明确我的要求,如果有更好的“不,你不能这样做”,我仍然需要我的问题的答案;)