Темы и делегаты - я не до конца понимаю их отношения - PullRequest
16 голосов
/ 25 сентября 2010

Я написал код, который выглядит примерно так:

Thread t = new Thread(() => createSomething(dt, start, finish) );
t.Start();

И это работает (иногда кажется, что существует несколько потоков).

Пока я не использую никаких делегатов.

  1. Что означает протектор без делегата?
  2. Если необходим делегат, то, пожалуйста, скажите мне, что и как установлено с делегатом.

Ответы [ 2 ]

41 голосов
/ 25 сентября 2010

Многопоточность очень сложна. Вы вырезаете и вставляете код, даже не изучая ничего о самых основных аспектах многопоточности - как запустить поток. Вставка чего-либо из Интернета в пользовательский интерфейс для исправления или настройки элемента управления - это одно. Это совершенно другой процесс. Вам нужно изучить предмет, написать весь собственный код и точно понять, как он работает, иначе вы просто тратите время на это.

Делегат - это .NET-версия указателя безопасной функции типа. Все потоки требуют точки входа, чтобы начать выполнение. По определению, когда создается основной поток, он всегда запускает Main () в качестве точки входа. Любые дополнительные потоки, которые вы создадите, будут нуждаться в явно определенной точке входа - указателе на функцию, с которой они должны начать выполнение. Таким образом, потоки всегда требуют делегата.

Делегаты часто используются в потоках и для других целей, в основном для обратных вызовов. Если вы хотите, чтобы поток сообщал некоторую информацию, такую ​​как состояние завершения, одной из возможностей является создание функции обратного вызова, которую этот поток может использовать. Снова потоку нужен указатель, чтобы иметь возможность выполнить обратный вызов, поэтому делегаты также используются для этого. В отличие от точки входа они являются необязательными, но концепция та же самая.

Отношения между потоками и делегатами - вторичные потоки не могут просто вызывать методы, подобные первичному потоку приложения, поэтому вместо них необходим указатель функции, а делегаты действуют как указатели на функции.

Вы не видите делегата и не создали его, потому что среда делает это за вас в конструкторе Thread. Вы можете передать метод, который хотите использовать для запуска потока, и код платформы создаст делегат, который указывает на этот метод для вас. Если вы хотите использовать обратный вызов, вам придется создать делегата самостоятельно.

Вот код без лямбда-выражений. SomeClass имеет некоторую обработку, которая занимает много времени и выполняется в фоновых потоках. Чтобы помочь с этим, был создан SomeThreadTask, который содержит код процесса и все, что потоку нужно для его запуска. Второй делегат используется для обратного вызова, когда поток завершен.

Реальный код был бы более сложным, и реальный класс никогда не должен знать, как создавать потоки и т. Д., Чтобы у вас были объекты менеджера.

// Create a delegate for our callback function.
public delegate void SomeThreadTaskCompleted(string taskId, bool isError);


public class SomeClass
{

    private void DoBackgroundWork()
    {
        // Create a ThreadTask object.

        SomeThreadTask threadTask = new SomeThreadTask();

        // Create a task id.  Quick and dirty here to keep it simple.  
        // Read about threading and task identifiers to learn 
        // various ways people commonly do this for production code.

        threadTask.TaskId = "MyTask" + DateTime.Now.Ticks.ToString();

        // Set the thread up with a callback function pointer.

        threadTask.CompletedCallback = 
            new SomeThreadTaskCompleted(SomeThreadTaskCompletedCallback);


        // Create a thread.  We only need to specify the entry point function.
        // Framework creates the actual delegate for thread with this entry point.

        Thread thread = new Thread(threadTask.ExecuteThreadTask);

        // Do something with our thread and threadTask object instances just created
        // so we could cancel the thread etc.  Can be as simple as stick 'em in a bag
        // or may need a complex manager, just depends.

        // GO!
        thread.Start();

        // Go do something else.  When task finishes we will get a callback.

    }

    /// <summary>
    /// Method that receives callbacks from threads upon completion.
    /// </summary>
    /// <param name="taskId"></param>
    /// <param name="isError"></param>
    public void SomeThreadTaskCompletedCallback(string taskId, bool isError)
    {
        // Do post background work here.
        // Cleanup the thread and task object references, etc.
    }
}


/// <summary>
/// ThreadTask defines the work a thread needs to do and also provides any data 
/// required along with callback pointers etc.
/// Populate a new ThreadTask instance with any data the thread needs 
/// then start the thread to execute the task.
/// </summary>
internal class SomeThreadTask
{

    private string _taskId;
    private SomeThreadTaskCompleted _completedCallback;

    /// <summary>
    /// Get. Set simple identifier that allows main thread to identify this task.
    /// </summary>
    internal string TaskId
    {
        get { return _taskId; }
        set { _taskId = value; }
    }

    /// <summary>
    /// Get, Set instance of a delegate used to notify the main thread when done.
    /// </summary>
    internal SomeThreadTaskCompleted CompletedCallback
    {
        get { return _completedCallback; }
        set { _completedCallback = value; }
    }

    /// <summary>
    /// Thread entry point function.
    /// </summary>
    internal void ExecuteThreadTask()
    {
        // Often a good idea to tell the main thread if there was an error
        bool isError = false;

        // Thread begins execution here.

        // You would start some kind of long task here 
        // such as image processing, file parsing, complex query, etc.

        // Thread execution eventually returns to this function when complete.

        // Execute callback to tell main thread this task is done.
        _completedCallback.Invoke(_taskId, isError);


    }

}
}
25 голосов
/ 25 сентября 2010

Вы используете , используя делегата - это всего лишь синтаксический сахар C # для:

Thread t = new Thread(new ThreadStart( () => createSomething(dt, start, finish))); 
t.Start();

Компилятор выводит из лямбда-выражения и различных перегрузок, которые имеет конструктор Thread, которые вы намереваетесь:

  • Создание экземпляра делегата ThreadStart.
  • Передать его в качестве аргумента перегрузке конструктора Thread, которая принимает объект ThreadStart.

Вы также можете эквивалентно написать это с синтаксисом анонимного делегата:

 Thread t = new Thread(delegate() { createSomething(dt, start, finish); } ); 
 t.Start();

Если аргументы createSomething не являются (захваченными) локальными, вы могли бы написать это вообще без анонимных методов, что должно более четко освещать создание делегата:

private void Create()
{
   createSomething(dt, start, finish))); 
}

...

Thread t = new Thread(new ThreadStart(Create)); //new ThreadStart is optional for the same reason 
t.Start();
...