Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
374 views
in Technique[技术] by (71.8m points)

c# - Rising events without blocking and receiving events in the right order

This required some explanation first. There is a working thread which has to rise some event:

Task.Run(() =>
{
    for(int i = 0; i < 123456789; i++)
    {
        ... // some job
        OnSomeEvent(i);
    }
});

Rising events synchronously will block the job until all event handlers have finished:

void OnSomeEvent(int i) => SomeEvent?.Invoke(this, new SomeEventArgs(i));

Asynchronous event rising will not block the job anymore (yay!)

void OnSomeEvent(int i) => Task.Run(() => SomeEvent?.Invoke(this, new SomeEventArgs(i)));

but now there is another problem: events are not received in the right order:

OnSomeEvent(1);
OnSomeEvent(2);
OnSomeEvent(3);
...

// event handler
SomeEvent += (s, e) => Console.WriteLine(e.I);

// possible output
1
3
2

Question: how to implement asynchronous events rising which occurs in the right order?

Recently I learned what Dispatcher.InvokeAsync uses queue. It looks like I have to do something similar. And if I have to then: 1) should it be a job of a caller or 2) should I keep rising events synchronously and receiver has to organize producer/consumer to prevent job from blocking? Or maybe there is another way?

P.S.: this has nothing to do with ContinueWhith.. unless storing list of tasks is a proper solution. My concern is how to implement fire-and-forget events where: a) caller is not blocked 2) events are received in same order.

P.P.S.: I don't know how to make MCVE to reproduce the problem. It appears in real project with heavy UI, lots of threads, etc.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can use the following TaskQueue in order to add asynchronous operations to a queue so that each one is started when the previous item in the queue finishes:

public class TaskQueue
{
    private Task previous = Task.FromResult(false);
    private object key = new object();

    public Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
    {
        lock (key)
        {
            var next = previous.ContinueWith(t => taskGenerator()).Unwrap();
            previous = next;
            return next;
        }
    }
    public Task Enqueue(Func<Task> taskGenerator)
    {
        lock (key)
        {
            var next = previous.ContinueWith(t => taskGenerator()).Unwrap();
            previous = next;
            return next;
        }
    }
}

This allows you to write:

private TaskQueue taskQueue = new TaskQueue();
private void OnSomeEvent(int i) => 
    taskQueue.Enqueue(() => Task.Run(() => SomeEvent?.Invoke(this, new SomeEventArgs(i))));

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...