samedi 11 juin 2016

IComparable.CompareTo - Implement asynchronous compare that waits on button press

I'm writing a small app that helps me sort pictures in order of their subjective quality. I'm not interested in implementing a machine learning solution, so I decided to go with a user-directed sort in which the user is presented with pairs of pictures and is required to select which of the pair is the better picture (or if they're both at the same quality).

Instead of trying to split something like quicksort or insertion sort in half, I decided to go with the existing OrderBy LINQ method, which works on objects implementing IComparable<T>. The desired code path is as follows

  1. OrderBy calls CompareTo on my UserSortable<T>.
  2. CompareTo calls two callbacks that sets each PictureBox so the user can see them.
  3. CompareTo then waits until the user makes a selection. The PictureBox.Click event handler tells something like a CancellationTokenSource to stop waiting.
  4. Which PictureBox was clicked is stored as a property on the form that the UserSortable<T> has a reference to - its value is checked and used as the result of CompareTo.

The problem, as of now, is in step 3. CancellationTokenSource does support cancellation from the form's click event, but it throws a TaskCanceledException as I try to actually get the result.

Additionally, because int IComparable<T>.CompareTo(T) is an existing interface, I cannot await call anything from it, leading me to do the hackish GetAwaiter().GetResult() method.

What's the best way to go about implementing this?

Code: UserSortable<T>

internal sealed class UserSortable<T> : IComparable<UserSortable<T>>
    {
        private UserDirectedSortForm form;
        private Action<T> setFirstItem;
        private Action<T> setSecondItem;
        private CancellationTokenSource cancellationToken;
        internal T Item { get; private set; }

        public UserSortable(UserDirectedSortForm form, T item, Action<T> setFirstItem, Action<T> setSecondItem)
        {
            this.form = form;
            this.Item = item;
            this.setFirstItem = setFirstItem;
            this.setSecondItem = setSecondItem;
        }

        public async Task CompareTo(UserSortable<T> other)
        {
            setFirstItem(Item);
            setSecondItem(other.Item);

            form.CancellationToken = cancellationToken = new CancellationTokenSource();
            await WaitForUserSelection();
        }

        private async Task<int> AssignResult(UserSortable<T> other)
        {
            await CompareTo(other);
            int result = 0;

            if (form.Selected == 'A') { result = -1; }
            else if (form.Selected == 'B') { result = 1; }
            else if (form.Selected == '=') { result = +1; }
            else { result = int.MaxValue; }

            return result;
        }

        private Task WaitForUserSelection()
        {
            return Task.Delay(-1, cancellationToken.Token);
        }

        int IComparable<UserSortable<T>>.CompareTo(UserSortable<T> other)
        {
            return AssignResult(other).GetAwaiter().GetResult();
        }
    }

UserDirectedSortForm

public partial class UserDirectedSortForm : Form
    {
        internal CancellationTokenSource CancellationToken { get; set; }
        private List<UserSortable<int>> items;
        internal char Selected { get; set; }

        public UserDirectedSortForm()
        {
            InitializeComponent();

            int first = 0;
            int second = 0;

            Action<int> setFirst = i => first = i;
            Action<int> setSecond = i =>
            {
                second = i;
                string newText = $"Which is larger? {first} or {second}?";
                Invoke((MethodInvoker)delegate { label1.Text = newText; });
            };

            items = new List<UserSortable<int>>();
            items.Add(new UserSortable<int>(this, 0, setFirst, setSecond));
            items.Add(new UserSortable<int>(this, 4, setFirst, setSecond));
            items.Add(new UserSortable<int>(this, 12, setFirst, setSecond));
            items.Add(new UserSortable<int>(this, 3, setFirst, setSecond));
            items.Add(new UserSortable<int>(this, 13, setFirst, setSecond));
            items.Add(new UserSortable<int>(this, 4, setFirst, setSecond));
            items.Add(new UserSortable<int>(this, 26, setFirst, setSecond));
            items.Add(new UserSortable<int>(this, 543221, setFirst, setSecond));
        }

        private void AssignResult(char result)
        {
            Selected = result;

            try
            {
                using (CancellationToken)
                {
                    CancellationToken.Cancel();
                }

                CancellationToken = null;
            }
            catch (TaskCanceledException ex)
            {
                ;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            AssignResult('A');
        }

        private void button2_Click(object sender, EventArgs e)
        {
            AssignResult('B');
        }

        private void button3_Click(object sender, EventArgs e)
        {
            AssignResult('=');
        }

        private void label1_Click(object sender, EventArgs e)
        {
            Task.Run(() =>
            {
                StringBuilder builder = new StringBuilder();
                foreach (var item in items.OrderBy(i => i))
                {
                    builder.Append(item.Item);
                    builder.Append(", ");
                }
                MessageBox.Show(builder.ToString());
            });
        }
    }

Aucun commentaire:

Enregistrer un commentaire