C# 调用内部任务。运行时,如何解决死锁?

C# 调用内部任务。运行时,如何解决死锁?,c#,wpf,multithreading,async-await,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 But

我有一个静态方法,可以从任何地方调用。在执行过程中,它将遇到
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()

它能实现吗

注意:为了简短地回答问题,我没有附上我的各种
async/await
尝试以及一个用于UI调用的尝试,但是使用一个看起来很糟糕。

你不能

即使这三项要求中只有两项不能同时实现——“此方法应阻止调用方”与“此方法不应冻结UI”相冲突

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


只需重申您已经知道的内容-您不能阻止线程并在许多问题(如-)中讨论的同时调用它。

要实现
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);
}
公共类数据
{
公共对象锁{get;}=new object();
公共bool已完成{get;set;}
}
公共静态布尔测试(字符串文本)
{
var data=新数据();
Task.Run(()=>
{
Thread.Sleep(1000);//模拟工作
App.Current.Dispatcher.Invoke(()=>{});
锁(data.lock)
{
data.IsFinished=true;
Monitor.Pulse(data.Lock);//唤醒
}
});
if(App.Current.Dispatcher.CheckAccess())
而(!data.IsFinished)
DoEvents();
其他的
锁(data.lock)
Monitor.Wait(data.Lock);
返回false;
}
静态void DoEvents()//用于wpf
{
var frame=新DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,新函数(o=>
{
((DispatcherFrame)o).Continue=false;
返回null;
}),框架);
调度员.推架(框架);
}
想法很简单:检查当前线程是否需要调用(UI线程),然后运行
DoEvents
循环或阻塞线程

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


它可以工作(虽然没有完全测试),但它很糟糕。我希望这能明确我的要求,如果有更好的“不,你不能这么做”,我仍然需要我问题的答案。)

@PoweredByOrange访问
.Result
属性是阻塞调用。删除
.Result
并使返回类型为
任务
,然后调用代码可以
等待结果。这将导致调用代码暂停,然后在获得结果后继续。这是否符合要求?如果在UI线程内调用,您的代码将阻止UI。如果要释放UI线程,有两个选项:
async
事件处理程序或
BackgroundWorker
@FedericoDipuma BGW已过时,根本不需要事件处理程序。使用.NET 4.5+中的
IProgress
类执行进度报告。或者只需在UI和真正异步的部分中分解方法,并使用
await
等待异步的执行parts@Sinatr你想干什么?为什么要尝试从任务内部修改UI?为什么不使用异步事件处理程序呢?谢谢你的回答,尽管我今天早上不希望只说“不可能”作为答案;)我提到
MessageBox
是有原因的,也许我的需求没有明确描述?我想从UI线程或某个任务调用
Test()
,它应该在不冻结UI的情况下阻止调用者。我将发布我拙劣的(但令人惊讶的工作)尝试作为答案,请看一看。@Sinatr编写适用于所有类型消息的消息泵,同时不会导致意外的重新进入问题是非常困难的。我不会自己做,也不会向任何人推荐。至少要仔细阅读中的警告。我知道
DoEvents()
的问题,了解替代方案很有意思。目前,我正试图通过不必调用任何东西来避免这种特殊情况。尽管这个问题很糟糕(它被否决了,并且缺少重要的细节-
Task
返回了一些需要调用的内容,但我仍然不能接受“No”作为答案,对不起;)我宁愿把我自己的答案标记为一个(尽管我不会,因为它测试得很差)。所有这些代码的意义是什么?使用
async/await
您不需要这些。真正的问题是,您仍然没有显示任何在后台运行的需要报告的内容。您在这里编写的内容启动一个任务只是为了阻止它并尝试回调UI线程。好吧,如果在任务中没有其他事情要做,就不要做,而是留在UI线程中。@PanagiotisKanavos,谢谢,现在我知道我错过了什么。注意返回值?它不会立即可用(在这个代码段中,我只是返回
false
,甚至没有从任务中传递它)。代码现在会阻止调用者(UI或非UI线程),直到结果变为可用,然后调用者应该收到结果并继续。能否以不同的方式实现?这是调用
Set时
TaskCompletionSource
正在做的事情