等待运算符没有像我预期的那样等待

2024-04-02

我正在上课DelayedExecutor这将延迟执行Action传递给其DelayExecute方法按一定时间timeout(参见下面的代码)使用 async 和await 语句。我还希望能够中止执行timeout如果需要的话间隔。我编写了一个小测试来测试其行为,如下所示:

代码为测试方法:

    [TestMethod]
    public async Task DelayExecuteTest()
    {
        int timeout = 1000;
        var delayExecutor = new DelayedExecutor(timeout);
        Action func = new Action(() => Debug.WriteLine("Ran function!"));
        var sw = Stopwatch.StartNew();
        Debug.WriteLine("sw.ElapsedMilliseconds 1: " + sw.ElapsedMilliseconds);
        Task delayTask = delayExecutor.DelayExecute(func);
        await delayTask;
        Debug.WriteLine("sw.ElapsedMilliseconds 2: " + sw.ElapsedMilliseconds);
        Thread.Sleep(1000);
    }
}

我期望此测试的输出是它会向我显示:

DelayExecute 1 之外的 sw.ElapsedMilliseconds:...

行动起来!”

DelayExecute 内的 sw.ElapsedMilliseconds:...

DelayExecute 2 之外的 sw.ElapsedMilliseconds:

但是我明白了这一点,但不明白为什么:

sw.ElapsedMilliseconds 超出 DelayExecute 1: 0

sw.ElapsedMilliseconds 超出 DelayExecute 2: 30

跑行动!

DelayExecute 内的 sw.ElapsedMilliseconds:1015

On this 博客文章 http://blog.stephencleary.com/2012/02/async-and-await.html I read:

我喜欢将“等待”视为“异步等待”。也就是说,异步方法会暂停,直到等待完成(因此它会等待),但实际线程并未被阻塞(因此它是异步的)。

这似乎符合我的预期,那么这是怎么回事,我的错误在哪里?

代码为延迟执行器:

public class DelayedExecutor
{
    private int timeout;

    private Task currentTask;
    private CancellationToken cancellationToken;
    private CancellationTokenSource tokenSource;

    public DelayedExecutor(int timeout)
    {
        this.timeout = timeout;
        tokenSource = new CancellationTokenSource();
    }

    public void AbortCurrentTask()
    {
        if (currentTask != null)
        {
            if (!currentTask.IsCompleted)
            {
                tokenSource.Cancel();
            }
        }
    }

    public Task DelayExecute(Action func)
    {
        AbortCurrentTask();

        tokenSource = new CancellationTokenSource();
        cancellationToken = tokenSource.Token;

        return currentTask = Task.Factory.StartNew(async () =>
            {
                var sw = Stopwatch.StartNew();
                await Task.Delay(timeout, cancellationToken);
                func();
                Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
            });
    }
}

Update:

按照建议,我在我的内部修改了这一行DelayExecute

return currentTask = Task.Factory.StartNew(async () =>

into

return currentTask = await Task.Factory.StartNew(async () =>

为了这个工作,我需要将签名更改为这个

public async Task<Task> DelayExecute(Action func)

所以我的新定义是这样的:

public async Task<Task> DelayExecute(Action func)
{
    AbortCurrentTask();

    tokenSource = new CancellationTokenSource();
    cancellationToken = tokenSource.Token;

    return currentTask = await Task.Factory.StartNew(async () =>
        {
            var sw = Stopwatch.StartNew();
            await Task.Delay(timeout, cancellationToken);
            func();
            Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
        });
}

然而现在我的行​​为和以前一样。有什么方法可以实现我想要使用我的设计做的事情吗?或者说它有根本性的缺陷吗?

顺便说一下,我也尝试过await await delayTask;在我的测试中DelayExecuteTest,但这给了我错误

无法等待“无效”

这是更新的测试方法DelayExecuteTest, which does not编译:

public async Task DelayExecuteTest()
{
    int timeout = 1000;
    var delayExecutor = new DelayedExecutor(timeout);
    Action func = new Action(() => Debug.WriteLine("Ran Action!"));
    var sw = Stopwatch.StartNew();
    Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 1: " + sw.ElapsedMilliseconds);
    Task delayTask = delayExecutor.DelayExecute(func);
    await await delayTask;
    Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 2: " + sw.ElapsedMilliseconds);
    Thread.Sleep(1000);
}

这里发生了一些事情,所以我将首先发布一个简单的答案,让我们看看它是否足够。

我们将一个任务包装在一个任务中,这需要双重等待。否则,您只是等待内部任务到达其第一个返回点,该返回点将(可以)位于第一个await.

让我们看看你的DelayExecute方法更详细。让我从更改方法的返回类型开始,我们将看到这如何改变我们的观点。返回类型的更改只是一个说明。您正在返回一个Task现在这是非通用的,实际上你正在返回一个Task<Task>.

public Task<Task> DelayExecute(Action func)
{
    AbortCurrentTask();

    tokenSource = new CancellationTokenSource();
    cancellationToken = tokenSource.Token;

    return currentTask = Task.Factory.StartNew(async () =>
        {
            var sw = Stopwatch.StartNew();
            await Task.Delay(timeout, cancellationToken);
            func();
            Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
        });
}

(请注意,这不会编译,因为currentTask也是类型Task,但是如果您阅读了问题的其余部分,这在很大程度上是无关紧要的)

好的,那么这里会发生什么?

让我们先解释一个更简单的例子,我在中测试了这个LINQPad http://linqpad.net:

async Task Main()
{
    Console.WriteLine("before await startnew");
    await Task.Factory.StartNew(async () =>
    {
        Console.WriteLine("before await delay");
        await Task.Delay(500);
        Console.WriteLine("after await delay");
    });
    Console.WriteLine("after await startnew");
}

执行此操作时,我得到以下输出:

before await startnew
before await delay
after await startnew
after await delay              -- this comes roughly half a second after previous line

那么为什么会这样呢?

嗯,你的StartNew返回一个Task<Task>。这不是一个现在将等待内部任务完成的任务,它等待内部任务完成return,它首先会(可以)做await.

因此,让我们通过对有趣的行进行编号,然后解释事情发生的顺序来查看完整的执行路径:

async Task Main()
{
    Console.WriteLine("before await startnew");           1
    await Task.Factory.StartNew(async () =>               2
    {
        Console.WriteLine("before await delay");          3
        await Task.Delay(500);                            4
        Console.WriteLine("after await delay");           5
    });
    Console.WriteLine("after await startnew");            6
}

1.第一个Console.WriteLine执行

这里没什么神奇的

2. 我们启动一个新任务Task.Factory.StartNew并等待它。

这里我们的main方法现在可以返回,它将返回一个任务,一旦内部任务完成,该任务将继续。

内部任务的工作是not执行all该委托中的代码。内部任务的工作是产生另一个任务.

这个很重要!

3. 内部任务现在开始执行

它本质上会执行委托中的所有代码,包括调用Task.Delay,它返回一个Task.

4. 内部任务首先到达await

由于这尚未完成(它只会在大约 500 毫秒后完成),因此该委托现在返回.

基本上,await <this task we got from Task.Delay>语句将创建一个延续,然后返回。

这个很重要!

6. 我们的外部任务现在继续

由于内部任务返回,外部任务现在可以继续。调用的结果await Task.Factory.StartNew又是另一项任务,但是这项任务只能靠自己解决了.

5. 内部任务,大约持续500ms后

现在是在外部任务已经继续执行之后,可能已经完成执行。


所以总而言之,你的代码“按预期”执行,只是不是这样you预期的。

有多种方法可以修复此代码,我只是重写您的DelayExecute方法以最简单的方式做你想做的事:

更改这一行:

    return currentTask = Task.Factory.StartNew(async () =>

进入这个:

    return currentTask = await Task.Factory.StartNew(async () =>

这将使内部任务启动秒表并到达第一个等待,然后一路返回DelayExecute.

与我类似的修复LINQPad上面的例子就是简单地改变这一行:

await Task.Factory.StartNew(async () =>

To this:

await await Task.Factory.StartNew(async () =>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

等待运算符没有像我预期的那样等待 的相关文章

随机推荐

  • 使用一对多关系在 Django 中创建 2 种不同的用户类型

    我正在创建一个篮球统计应用程序 我希望有两种不同的用户类型 教练和球员 我希望教练能够登录并仅查看他的球员的统计数据 因此 一名教练将拥有多名球员 我希望能够为每个教练创建用户个人资料 并仅显示他们的球员 而不是数据库中的所有球员 到目前为
  • 谷歌地图数据层,点击多边形并更改不透明度(Chrome Bug)

    我有一个谷歌地图 我使用 addGeoJson 将几何数据 加载到数据层 一切正常 直到我尝试在多边形上绑定事件以更改 opactiy 设置 我从OSM检索城市边界来绘制多边形 我认为我的json对象格式很好 我以Google作为参考 ht
  • TypeScript 编译中缺少 in-memory-data.service.ts

    浏览 Angular 教程 了解如何在中使用 http 请求https angular io tutorial toh pt6 https angular io tutorial toh pt6 我到了这一点 使用以下命令生成类 src a
  • Sublime Doctype HTML 片段

    有没有办法可以将下面的 作为片段插入 我尝试使用 首选项 gt 键绑定 用户 但引号确实很重要 您可以通过创建一个新的代码片段Tools gt New Snippet
  • onSharedPreferenceChanged 不会一直触发

    我对这个方法有一个奇怪的问题 当我编辑 EditTextPreference 时 仅当我更改值时才调用它 而在 MultiSelectListPreference 上 仅当我第一次更改时才调用它 这是我的片段代码 public class
  • Excel 解算器具有非相邻单元格约束?

    我是 Excel 求解器的新手 只是在拿起一本数据科学书籍后才了解它 我想更熟悉这个工具 所以我一直在尝试解决不同的问题 但我被困在一个问题上 我什至不确定是否可以使用求解器 基本上 我需要检查的约束是两个单元格是否相邻 我的问题 我有一堆
  • 在 Excel 中计算唯一值

    我需要在 Excel 中计算范围 C2 C2080 中的唯一值 谷歌搜索公式 SUM IF FREQUENCY MATCH C2 C2080 C2 C2080 0 MATCH C2 C280 C2 C2080 0 gt 0 1 返回不正确的
  • 使用多层 - KineticJS

    我正在使用 KineticJS 4 0 5 目前正在尝试绘制几个图层的内容 但只绘制了添加到舞台的最后一个图层 如果我正确理解了文档 这应该是可能的 否则我们为什么需要一层 我有三个不同的层 仅包含 Kinetic Rect 对象的背景层
  • MPANDROIDCHART:如何使用 .setViewPortOffsets(0,0,0,0) 绘制 X 标签

    当我的图表已将 setViewPortOffsets 设置为 0 0 0 0 时 我在绘制 X 标签时遇到问题 我知道 setViewPortOffsets 的作用 但我不知道如何设置它以仅使左右边距消失边 有人能帮助我吗 PS 抱歉我的英
  • 逐行修改csv文件

    我有一个大文件 我想修改其中的每一行 我想使用 PHP 快速完成 我的文件是 CSV 文件 20010103 02 00 00 0 9496 20010103 03 00 00 0 9504 20010103 04 00 00 0 9499
  • 有没有办法观察 UIViewPropertyAnimator 中fractionComplete的变化

    我一直在看非常酷的新作品UIViewPropertyAnimatoriOS 10 中的类 它可以让你easily执行诸如暂停 恢复和反转飞行中 UIView 动画之类的操作 过去 您必须操作系统创建的底层 CAAnimations 才能对
  • Android BroadcastReceiver还是简单的回调方法?

    在我的项目中我正在使用BroadcastReceiver作为来自长时间运行的线程的回调 例如 通知活动下载已完成并从 Worker 发送一些响应数据Thread以便活动可以向用户显示适当的消息 使用BroadcastReceiver每次使用
  • :: C++ 中模板函数调用前面的范围解析运算符

    我被模板和范围解析运算符困住了 我在文件中找到了这些行 我无法弄清楚为什么我们在模板函数调用前面使用 据我所知 当引用全局变量时 我们只能在变量前面使用 任何想法都会有帮助 define CREATE AND DECODE TYPE Typ
  • 如何用绝对路径 URL 替换相对路径 URL

    我将 HTML 内容存储在数据库中 并且希望将所有相对资源引用转换为使用绝对路径 例如 我的所有图像标签看起来都是这样的 img src 我正在尝试前置 http example com to the system images 小路 我有
  • 获取物体的位置,最多精确到小数点后 3 位

    我通过单击鼠标来实例化一个对象 我需要将变换位置 x 和 y 保留到小数点后 3 位 这是我的代码 void OnMouseDown ray Camera main ScreenPointToRay Input mousePosition
  • 创建一个独立于顺序自动创建新组的函数

    I asked 这个问题 https stackoverflow com questions 73894399 creating new groups when the original groups do not have suffici
  • 折叠表达式的结合性

    N4191 http www open std org jtc1 sc22 wg21 docs papers 2014 n4191 html建议的 C 折叠表达式 那里的定义是 args 是左折 即 a0 a1 a2 然后 args 是右折
  • 是否可以使用 Dropbox 托管一个裸 Git 存储库来共享代码?

    我意识到有类似的问题 https stackoverflow com questions 1960799 using gitdropbox together effectively 但我的问题略有不同 我想知道是否共享裸存储库通过多台计算机
  • 如何在asp.net c#中仅获取不包括时间的日期

    如何在asp net c 中仅获取不包括时间的日期 我只想将日期作为搜索输入 例如 3 11 2013 您可以使用日期时间 日期 http msdn microsoft com en us library system datetime d
  • 等待运算符没有像我预期的那样等待

    我正在上课DelayedExecutor这将延迟执行Action传递给其DelayExecute方法按一定时间timeout 参见下面的代码 使用 async 和await 语句 我还希望能够中止执行timeout如果需要的话间隔 我编写了