سؤال

I have the following C# code.

var databaseRestore = new Microsoft.SqlServer.Management.Smo.Restore();
//databaseRestore.PercentComplete += CompletionStatusInPercent;
//databaseRestore.PercentCompleteNotification = 10;
//databaseRestore.Complete += Restore_Completed;

...

var complete = Observable
    .FromEventPattern(databaseRestore, "Complete")
    .Select(x=>x.EventArgs as ServerMessageEventArgs)
    .Select(x=>x.Error.Message)
    .Take(1)
    .DumpLive("Complete");

var percentComplete = Observable
    .FromEventPattern(databaseRestore, "PercentComplete")
    .Select(x=>x.EventArgs as PercentCompleteEventArgs)
    .Select(x=>x.Percent)
    .TakeUntil(complete)
    .DumpLive("PercentComplete");

...

databaseRestore.SqlRestore(server);

If I run it this, first comes always the output from handlers (if I uncomment them).

Then first, Linqpad shows the "Live Observables" result tab with

  1. "Complete" observable, already finished and with final result message.
  2. "PercentComplete", still waiting and with "-" (yet no entries?)

What I want is just to come away from events using reactive extenxions. First should come the "PercentComplete" observable updated with actual progress. Then "Complete" with final message.

The question: how do I set up the observables properly?

هل كانت مفيدة؟

المحلول 2

My guess is this is the fault of how DumpLive() uses a Dispatcher on the main thread.

Are you running the database restore on the main thread, or blocking the main thread waiting for a completion signal?

I experimented a bit and ran an Observable on a background thread with a DumpLive() - if the main thread was blocked (e.g. with a Wait or a Thread.Sleep) the UI did not update.

Even if it's on a different thread, it seems DumpLive() can only update the UI on the main thread. My guess it that it needs to render updates on a Dispatcher associated with the main thread.

When using DumpLive LINQPad won't terminate execution until the observables complete, so if you have any blocking code on main thread remove it and see if it works then.

Here's my experimental code. As soon as you add in either DumpLive() clause in the example below, you'll see the blocking behaviour:

void Main()
{
    Task.Run(() => {
    var progressor = new Progressor();
    var complete = Observable
        .FromEventPattern(
        h => progressor.Complete += h,
        h => progressor.Complete -= h)
        .Select(_ => "Complete")
        .Take(1);
        // append this to see blocking
        //.DumpLive();

        complete.Subscribe(Console.WriteLine);


    var percentComplete = Observable
        .FromEventPattern<PercentCompleteEventArgs>(
        h => progressor.PercentComplete += h,
        h => progressor.PercentComplete -= h)
        .TakeUntil(complete)
        .Select(i => "PercentComplete " + i.EventArgs.PercentComplete);
        // append this to see blocking
        // .DumpLive();

        percentComplete.Subscribe(Console.WriteLine);
    });

    Thread.Sleep(5000);
}

public class Progressor
{
    public Progressor()
    {
        Observable.Interval(TimeSpan.FromSeconds(1)).Take(10)
            .Subscribe(
            i => RaisePercentComplete(((int)i+1) * 10),
            () => RaiseComplete());
    }

    private void RaiseComplete()
    {
        var temp = Complete;
        if(temp != null)
        {
            temp(this, EventArgs.Empty);
        }
    }

    private void RaisePercentComplete(int percent)
    {
        var temp = PercentComplete;
        if(temp != null)
        {
            temp(this, new PercentCompleteEventArgs(percent));
        }
    }

    public event EventHandler Complete;
    public event EventHandler<PercentCompleteEventArgs> PercentComplete;
}

public class PercentCompleteEventArgs : EventArgs
{
    public int PercentComplete { get; private set; }

    public PercentCompleteEventArgs(int percent)
    {
        PercentComplete = percent;
    }
}

نصائح أخرى

LINQPad's DumpLive method renders using WPF, so it cannot work if the main thread is blocked.

You could instead write your own version of DumpLive which renders using HTML. This will be slower (because it has to update an HTML DOM each time an observable posts a value), but it would work whether or not the main thread is blocked.

Here is the code:

IDisposable DumpLatest<T> (IObservable<T> obs)
{
    var dc = new DumpContainer ().Dump();
    var extensionToken = Util.GetQueryLifeExtensionToken();
    return obs.Subscribe (
        value => dc.Content = value,
        ex => { dc.Content = ex; extensionToken.Dispose(); },
        () => extensionToken.Dispose());
}

If you define it as an extension method in My Extensions, you will be able to call upon it whenever you need.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top