Skip to content

Instantly share code, notes, and snippets.

@StephenCleary
Last active April 16, 2024 11:22
Show Gist options
  • Save StephenCleary/4248e50b4cb52b933c0d to your computer and use it in GitHub Desktop.
Save StephenCleary/4248e50b4cb52b933c0d to your computer and use it in GitHub Desktop.
ObservableProgress
using System;
using System.Reactive.Linq;
using System.Threading;
/// <summary>
/// Helper methods for using observable <see cref="IProgress{T}"/> implementations. These are hot observables.
/// </summary>
public static class ObservableProgress
{
/// <summary>
/// Creates an observable and an <see cref="IProgress{T}"/> implementation that are linked together, suitable for UI consumption.
/// When progress reports are sent to the <see cref="IProgress{T}"/> implementation, they are sampled according to <paramref name="sampleInterval"/> and then forwarded to the UI thread.
/// This method must be called from the UI thread.
/// </summary>
/// <typeparam name="T">The type of progress reports.</typeparam>
/// <param name="sampleInterval">How frequently progress reports are sent to the UI thread.</param>
public static (IObservable<T> Observable, IProgress<T> Progress) CreateForUi<T>(TimeSpan? sampleInterval = null)
{
var (observable, progress) = Create<T>();
observable = observable.Sample(sampleInterval ?? TimeSpan.FromMilliseconds(100))
.ObserveOn(SynchronizationContext.Current);
return (observable, progress);
}
/// <summary>
/// Creates an observable and an <see cref="IProgress{T}"/> implementation that are linked together.
/// When progress reports are sent to the <see cref="IProgress{T}"/> implementation, they are immediately and synchronously sent to the observable.
/// </summary>
/// <typeparam name="T">The type of progress reports.</typeparam>
public static (IObservable<T> Observable, IProgress<T> Progress) Create<T>()
{
var progress = new EventProgress<T>();
var observable = Observable.FromEvent<T>(handler => progress.OnReport += handler, handler => progress.OnReport -= handler);
return (observable, progress);
}
private sealed class EventProgress<T> : IProgress<T>
{
public event Action<T> OnReport;
void IProgress<T>.Report(T value) => OnReport?.Invoke(value);
}
}
// Sample program is WPF, but could be any UI: WPF, WinForms, Xamarin Forms, WinRT, WinPhone, or Silverlight.
private async void Button_Click(object sender, RoutedEventArgs e)
{
var (observable, progress) = ObservableProgress.CreateForUi<int>();
using (observable.Subscribe(UpdateUi))
await Task.Run(() => Solve(progress));
void UpdateUi(int value)
{
// Update UI (if using MVVM, you should update ViewModels here, not set UI control contents directly)
Label.Content = value;
}
}
private void Solve(IProgress<int> progress)
{
int value = 0;
while (true)
{
value++;
progress?.Report(value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment