C#에서 Thread내에 Parallel.Foreach 가 있는 경우 중단 방법

January 09, 2017

dotnet

GUI 환경에서 버튼을 클릭하는 등의 이벤트로 시간이 오래 걸리는 작업을 구동하는 경우 스레드를 분리하여 개발하는 방법이 좋다는 방법은 C# 뿐만 아니라 안드로이드나 MFC 등 GUI를 어느 정도 개발한 사람이라면 익숙하리라 생각한다.

그렇다면 그 시간이 오래 걸리는 작업을 더욱더 빠르게 하고 싶다면 병렬 처리가 가장 쉽고 빨리 적용할 수 있는 합리적인 방법이라고 생각한다.

구글링을 통하여 어찌어찌 병렬 For 문을 적용하였지만, 문제는 “클라이언트가 구동은 했지만 오래 걸리는 프로세스를 작업 도중에 중단하고 싶다”라고 말했을 때 발생하였다.

다음은 몇 가지 코드를 조합하여 만든 메인 스레드와 분리된 다중 포문 작업 클래스와 그 작업을 중단시키는 예를 콘솔 응용프로그램으로 구성하였다. 1

메인 클래스 및 중단 클래스

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("press any key for Thread start!");
Console.ReadKey();

            RulyCanceler canceler = new RulyCanceler();
            TestFilter tf = new TestFilter(10000000);

            Thread t = new Thread(() =>
            {
                try
                {
                    tf.execute(canceler);
                }catch(OperationCanceledException)
                {
                    Console.WriteLine("Canceled!");
                }
            });
            t.Start();
            Console.ReadKey();
            Console.WriteLine("Abort!");

            canceler.Cancel();
            t.Abort();
            t.Join();

            Console.WriteLine("Aborted!");
            Console.ReadKey();

        }
    }
    public class RulyCanceler
    {
        object _cancelLocker = new object();
        bool _cancelRequest;
        public bool IsCancellationRequested
        {
            get { lock (_cancelLocker) return _cancelRequest; }
        }

        public void Cancel() { lock (_cancelLocker) _cancelRequest = true; }

        public void ThrowIfCancellationRequested()
        {
            if (IsCancellationRequested) throw new OperationCanceledException();
        }
    }

}

시간이 오래걸리는 작업 클래스

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ThreadTest
{
public class TestFilter
{
private long featureCount;
private int MULTI_THREAD_COUNT = 10;

        EventWaitHandle waitEvent = new EventWaitHandle(true, EventResetMode.AutoReset, "SHARED_BY_ALL_PROCESSES");
        public TestFilter(long featurecount)
        {
            this.featureCount = featurecount;
        }

        public void execute(RulyCanceler cancer)
        {
            int workingRangeSize = 1;
            if (featureCount > 100)
            {
                workingRangeSize = (int)(featureCount / MULTI_THREAD_COUNT);
            }
            var part = Partitioner.Create(0, featureCount, workingRangeSize);
            Parallel.ForEach(part, (num, state) =>
            {
                for (long featureIdx = num.Item1, cnt = num.Item2; featureIdx < cnt; featureIdx++)
                {
                    try
                    {

                        //waitEvent.WaitOne();(파일 쓰기모드일 경우 하나의 스레드만 쓸수 있도록 줄세우기)
                        //waitEvent.Set();

                        // 중단 요청이있으면 Throw
                        cancer.ThrowIfCancellationRequested();

                        // 실제 일
                        Console.WriteLine(String.Format("{0} - {1}", num.Item1, featureIdx));
                    }
                    catch (OperationCanceledException ex)
                    {
                        Console.WriteLine("Breaked.");
                        Console.WriteLine("Clean");
                        state.Break();
                        break;

                    }
                }
            });
        }
    }

}

@jeongyong-park

Written by @jeongyong-park Front-end React w/Typescript. Intereseted in Web Development, GIS, Data Science, and Data Visualization via 2d and 3d. You should follow him on Twitter

© 2022, Built with Gatsby and .