Интерфейс с многопоточностью

Вот допустим, есть большой бинарный файл. И есть кусочек, допустим, из 100 байт.
Нужно пройтись по большому файлу и, допустим, найти совпадение. Но не всегда нужно, чтобы совпадали именно 100 байт. Иногда бывает нужно пропустить сравнение некоторых байт. То есть, паттерн поиска может быть разный. Кажется, это ведь паттерн называется :thinking:
То есть, проще говоря, надо делать одно и то же, но по разному. Кажется, именно для этого существуют интерфейсы.
Должен быть интерфейс с методом Search(...) и разные реализации этого интерфейса. Что-то типа этого:

namespace ConsoleApp3
{
    internal interface ISearcher
    {
        void Search();
    }
}
namespace ConsoleApp3
{
    internal class Searcher1 : ISearcher
    {
        public void Search()
        {
            for (int i = 0; i < 99999999; ++i)
            {
                if (i % 10000 == 0)
                {
                    
                }
            }
        }
    }
}

Но как в интерфейсе запустить поток и как синхронизироваться с формой?
То есть, в конструктор реализатора интерфейса передавать контекст потока формы и блаблабла всё как обычно?

Накатал вот так:

        public async void Disassemble(Stream streamInput, Stream streamPrototype, Sync sync, Control context)
        {
            await Task.Run(() =>
            {
                long max = 99999999999L;
                for (long i = 0L; i < max; ++i)
                {
                    if (i % 100000 == 0)
                    {
                        context.Invoke((MethodInvoker)delegate
                        {
                            sync.Invoke(i, max);
                        });
                    }
                }
            });
        }

Вроде работает :man_shrugging:
Только непонятно, почему делегат Sync нельзя в самом интерфейсе прописать. Пришлось его в класс формы пихнуть.

Может лучше только сам поиск в нем оставить?

Так в нем и будет только поиск. Но надо ведь как-то уведомлять форму о состоянии прогресса :thinking:

Так это снаружи, там, где интерфейс вызывается.

Не понял :thinking: Поток же внутри реализатора интерфейса создаётся.
Или можно создать поток в классе формы и из него дёргать
за интерфейс?

Ну отличается же только сам поиск, так что можно сделать одну функцию, которая делает все общие вещи (управление потоком, …) и вызывает переданный интерфейс. Иначе зачем он вообще.

void Search(ISearcher searcher)
{
    ...
    Task.Run(() => {
        searcher.Search(input, result => ...Invoke....);
    }
}

Всё, я уже понял, спасибо. Чёт я подумал, что так нельзя. Но я немного не так как у вас сделал.

А это что? :thinking:

Ну вывод результата, вызов Invoke если надо.

Это я понял. Я не понял как это должно выглядеть :thinking:

searcher либо возвращает результат возвращаемым значением функции, либо принимает колбэк, в который результат передает. И та функция, которая searcher вызывала, выводит результат любым нужным способом.

Кажется, понял :thinking: Но у меня поиск должен найти не одно совпадение, а довольно много. И сам побайтовый перебор идёт крайне медленно (даже на маленьких файлах). А ведь есть и большие :man_facepalming:
Так что, всё-равно придётся синхронизироваться во время поиска, а не просто выдавать результат в конце :man_shrugging:

Ну так все равно лучше это всё в одном месте сделать, а не копипастить везде. А searcher просто вызывает колбэк, когда находит.

Не понял, что куда копипастить?

А как правильно колбэк объявить, чтобы его в метод поиска передать? Я вчера делал и оно работало, но какая-то фигня получилась. Потом переписал вот так:

namespace Stream_disassembler
{
    internal interface IStreamDisassembler
    {
        void Disassemble(Stream streamInput, Stream streamPrototype);
        bool Compare(byte[] bytesInput, byte[] bytesProto);
    }
}
namespace Stream_disassembler
{
    internal class TwitchDisassembler : IStreamDisassembler
    {
        public delegate void ProgressDelegate(long position, long max);
        public delegate void MatchFoundDelegate(long position);
        public ProgressDelegate ProgressChanged;
        public MatchFoundDelegate MatchFound;

        public void Disassemble(Stream streamInput, Stream streamPrototype)
        {
            int protoLen = (int)streamPrototype.Length;
            byte[] protoBytes = GetByteArray(streamPrototype, 0L, protoLen);
            for (long i = 0L; i < streamInput.Length - streamPrototype.Length; ++i)
            {
                byte[] buffer = GetByteArray(streamInput, i, protoLen);
                if (Compare(buffer, protoBytes))
                {
                    MatchFound?.Invoke(i);
                }
                if (i % 10000 == 0)
                {
                    ProgressChanged?.Invoke(i, streamInput.Length);
                }
            }
        }

        public bool Compare(byte[] bytesInput, byte[] bytesProto)
        {
            bool match = true;
            for (int i = 0; i < bytesProto.Length; ++i)
            {
                if (i != 3 && i != 191)
                {
                    match = match && (bytesInput[i] == bytesProto[i]);
                    if (!match)
                    {
                        break;
                    }
                }
            }
            return match;
        }

        private byte[] GetByteArray(Stream stream, long pos, int len)
        {
            stream.Position = pos;
            byte[] bytes = new byte[len];
            int read = stream.Read(bytes, 0, len);
            if (read < len)
            {
                return null;
            }
            return bytes;
        }
    }
}

И обработчики:

        private void OnProgress(long pos, long max)
        {
            if (InvokeRequired)
            {
                Invoke((MethodInvoker)delegate { OnProgress(pos, max); });
            }
            else
            {
                double percent = 100.0 / max * pos;
                string percentStr = string.Format("{0:F3}", percent);
                lblProgress.Text = $"{pos} / {max} ({percentStr}%)";
            }
        }

        private void OnMatchFound(long position)
        {
            if (position > 0L)
            {
                if (InvokeRequired)
                {
                    Invoke((MethodInvoker)delegate { OnMatchFound(position); });
                }
                else
                {
                    long size = position - _lastPos;
                    ListViewItem item = new ListViewItem(_lastPos.ToString());
                    item.SubItems.Add(position.ToString());
                    item.SubItems.Add(size.ToString());
                    listView1.Items.Add(item);
                    //listView1.EnsureVisible(listView1.Items.Count - 1);

                    _lastPos = position;
                    System.Diagnostics.Debug.WriteLine(position);
                }
            }
        }
    }

Так оно как-то получше выглядит, чем изначально было. А через колбэки как - чёт не понимаю :thinking:

Управление потоками, вывод прогресса в UI.

Например просто Action<...> с нужными типами параметров. Или IProgress.

То есть, вот так?

        void Disassemble(Stream streamInput, Stream streamPrototype, Action<long, long> progressAction);
        twitchDisassembler.Disassemble(inputStream, protoStream, OnProgress);

А обработчик такой же

        private void OnProgress(long pos, long max)
        {
            if (InvokeRequired)
            {
                Invoke((MethodInvoker)delegate { OnProgress(pos, max); });
            }
            else
            {
                double percent = 100.0 / max * pos;
                string percentStr = string.Format("{0:F3}", percent);
                lblProgress.Text = $"{pos} / {max} ({percentStr}%)";
            }
        }