我正在使用Observable /反应性扩展来消除某些事件的反弹,例如单击按钮或在文本框中输入文本。但是,在关闭或关闭的情况下,我需要等待所有未决事件,以便保存操作可以完成,等等。
以下代码将死锁。
Button b1 = new Button();
var scheduler = new EventLoopScheduler(ts => new Thread(ts)
{
IsBackground = false
});
var awaiter = Observable.FromEventPattern(h => b1.Click += h, h => b1.Click -= h, scheduler)
.Throttle(TimeSpan.FromMilliseconds(5000), scheduler)
.FirstOrDefaultAsync();
someTaskList.add(awaiter.ToTask());
awaiter.Subscribe
(
x =>
{
//do some work in response to click event
}
);
//program continues...
然后,在应用程序的其他地方
private async Task CloseApplicationSafely()
{
await AwaitPendingEvents();
}
private async Task AwaitPendingEvents()
{
if(someTaskList.Count > 0)
{
await Task.WhenAll(someTaskList);
}
}
然后该程序将死锁,如果从未发生按钮单击,则将永远等待。这是另一个示例,但是带有文本框。
var completedTask = Observable.FromEventPattern(h => t1.TextChanged += h, h => t1.TextChanged -= h, scheduler)
.Select(x => ((TextBox)x.Sender).Text)
.DistinctUntilChanged()
.Throttle(TimeSpan.FromMilliseconds(5000), scheduler)
.ForEachAsync(txt =>
{
//do some work, save the text
});
someTaskList.Add(completedTask);
在这种情况下,是否更改文本都没有关系。如果等待,变量completedTask将永远死锁。 ForEachAsync()返回一个似乎从未被激活的任务。
我在做什么错?希望我的预期功能清楚。我正在取消事件。但是,我需要等待正在进行反跳过程中的任何未决事件,以确保它们完成。并且,如果没有任何待处理的事件,则无需等待即可继续。谢谢。
答案 0 :(得分:0)
@Servy和@Enigmativity的评论帮助我确定了这一点。对于那些感兴趣的人,这是我想出的解决方案。关于我的方法的任何建议都会让我知道。
我创建了一个名为WaitableEventHelper的静态帮助器类,其中包括以下功能。
public static Task WaitableDebouncer(
this Control c,
Action<EventHandler> addHandler,
Action<EventHandler> removeHandler,
IScheduler scheduler,
CancellationToken cancelToken,
TimeSpan limit,
Func<Task> func)
{
var mycts = new CancellationTokenSource();
bool activated = false;
bool active = false;
Func<Task> pending = null;
var awaiter = Observable.FromEventPattern(addHandler, removeHandler, scheduler)
.TakeUntil(x => { return cancelToken.IsCancellationRequested; })
.Do((x) => { activated = true; })
.Do((x) =>
{
//sets pending task to last in sequence
pending = func;
})
.Throttle(limit, scheduler)
.Do((x) => { active = true; }) //done with throttle
.ForEachAsync(async (x) =>
{
//get func
var f = pending;
//remove from list
pending = null;
//execute it
await f();
//have we been cancelled?
if (cancelToken.IsCancellationRequested)
{
mycts.Cancel();
}
//not active
active = false;
}, mycts.Token);
//if cancelled
cancelToken.Register(() =>
{
//never activated, force cancel
if (!activated)
{
mycts.Cancel();
}
//activated in the past but not currently active
if (activated && !active)
{
mycts.Cancel();
}
});
//return new awaiter based on conditions
return Task.Run(async () =>
{
try
{
//until awaiter finishes or is cancelled, this will block
await awaiter;
}
catch (Exception)
{
//cancelled, don't care
}
//if pending isn't null, that means we terminated before ForEachAsync reached it
//execute it
if (pending != null)
{
await pending();
}
});
}
然后我像这样使用它。这是一个单击按钮的示例,b1是System.Windows.Forms.Button对象。这可以是任何东西。对于我的测试应用程序,我正在更改主窗体上某些面板的颜色。根据OP中的先前代码,任务只是Task类型的列表。
var awaiter1 = b1.WaitableDebouncer(h => b1.Click += h, h => b1.Click -= h,
scheduler,
canceller.Token,
TimeSpan.FromMilliseconds(5000),
async () =>
{
Invoke(new Action(() =>
{
if (p1.BackColor == Color.Red)
{
p1.BackColor = Color.Orange;
}
else if (p1.BackColor == Color.Orange)
{
p1.BackColor = Color.Yellow;
}
else if (p1.BackColor == Color.Yellow)
{
p1.BackColor = Color.HotPink;
}
else
{
p1.BackColor = Color.Red;
}
}));
});
tasks.Add(awaiter1);
另一个用于文本框中的TextChanged的方法。 t1是System.Windows.Forms.TextBox。同样,这可以是任何东西,我只是设置一个静态的someValue字符串变量并在UI上更新标签。
var awaiter2 = t1.WaitableDebouncer(h => t1.TextChanged += h, h => t1.TextChanged -= h,
scheduler,
canceller.Token,
TimeSpan.FromMilliseconds(5000),
async () =>
{
savedValue = t1.Text;
Invoke(new Action(() => l1.Text = savedValue));
});
tasks.Add(awaiter2);
这就是终止或关闭的样子。这可能是应用程序关闭,也可能是文件关闭。只是某些事件,我们需要取消绑定这些事件,但要保存用户在此之前启动的所有未决工作。想象一下,用户在文本框中输入内容,然后快速按X来关闭应用程序。 5秒还没有用完。
private async Task AwaitPendingEvents()
{
if (tasks.Count > 0)
{
await Task.WhenAll(tasks);
}
}
我们有一个应用程序范围内的等待例程。在结束时,我们这样做。
//main cancel signal
canceller.Cancel();
await AwaitPendingEvents();
到目前为止,对于我的测试来说,它似乎可以正常工作。如果没有事件发生,它将取消。如果已生成事件,那么我们将查看是否有任何尚未通过节流的未完成工作。如果是这样,我们取消可观察对象并自己执行待处理的工作,因此我们不必等待计时器。如果有待处理的工作,而我们已经通过节流,那么我们只需等待并让可观察的订阅完成执行。如果请求取消,则订阅将在执行后自行取消。