Asenkron Yöntem Yazma

Bu konuda, Microsoft Media Foundation'da zaman uyumsuz bir yöntemin nasıl uygulandığı açıklanmaktadır.

Zaman uyumsuz yöntemler Media Foundation işlem hattında yaygın olarak bulunur. Zaman uyumsuz yöntemler, çalışmayı birkaç iş parçacığı arasında dağıtmayı kolaylaştırır. Bir dosyadan veya ağdan okumanın işlem hattının geri kalanını engellememesi için G/Ç'yi zaman uyumsuz olarak gerçekleştirmek özellikle önemlidir.

Bir medya kaynağı veya medya havuzu yazıyorsanız, bileşeninizin performansı işlem hattının tamamını etkilediğinden zaman uyumsuz işlemleri doğru şekilde işlemek çok önemlidir.

Not

Media Foundation dönüşümleri (MFTs) varsayılan olarak zaman uyumlu yöntemleri kullanır.

 

Asenkron İşlemler için İş Kuyrukları

Media Foundation'da Zaman Uyumsuz Geri Çağırma Yöntemleri ile İş Kuyruklarıarasında yakın bir ilişki vardır. İş kuyruğu, işi çağıranın iş parçacığından bir çalışan iş parçacığına taşımaya yönelik bir soyutlamadır. Bir iş kuyruğunda iş gerçekleştirmek için aşağıdakileri yapın:

  1. IMFAsyncCallback arabirimini uygulayın.

  2. sonuç nesnesi oluşturmak için MFCreateAsyncResultçağır. Sonuç nesnesi, IMFAsyncResultaçığa çıkarır. Sonuç nesnesi üç işaretçi içerir:

    • Çağıranın IMFAsyncCallback arabirimine bir işaretçi.
    • Durum nesnesine yönelik isteğe bağlı bir işaretçi. Belirtilirse, durum nesnesinin IUnknownuygulaması gerekir.
    • Özel bir nesnenin isteğe bağlı işaretçisi. Belirtilirse, bu nesnenin de IUnknownuygulaması gerekir.

    Son iki işaretçi NULLolabilir. Aksi takdirde, bunları zaman uyumsuz işlemlerle ilgili bilgileri tutmak için kullanın.

  3. İş öğesini kuyruğa almak için MFPutWorkItemEx çağırın.

  4. İş kuyruğu iş parçacığı, IMFAsyncCallback::Invoke metodunuzu çağırır.

  5. Invoke yönteminizin içindeki işi yapın. Bu yöntemin pAsyncResult parametresi, 2. adımdaki IMFAsyncResult işaretçisidir. Durum nesnesini ve özel nesneyi almak için bu işaretçiyi kullanın:

Alternatif olarak, MFPutWorkItem işlevini çağırarak 2. ve 3. adımları birleştirebilirsiniz. Bu işlev, sonuç nesnesini oluşturmak için MFCreateAsyncResultdahili olarak çağırır.

Aşağıdaki diyagramda çağıran, sonuç nesnesi, durum nesnesi ve özel nesne arasındaki ilişkiler gösterilmektedir.

Zaman uyumsuz sonuç nesnesidiyagramı

Aşağıdaki sıralı diyagramda bir nesnenin bir iş öğesini nasıl kuyruğa alma işlemi gösterilmektedir. İş kuyruğu iş parçacığı Invokeçağırdığında, nesne bu iş parçacığında zaman uyumsuz işlemi gerçekleştirir.

nesnenin bir iş öğesini kuyruğa nasıl aldığını gösteren diyagramı

Invoke'nin, iş kuyruğuna ait olan bir iş parçacığından çağrıldığını hatırlamak önemlidir. Invoke uygulamanız iş parçacığı güvenli olmalıdır. Buna ek olarak, platform iş kuyruğunu (MFASYNC_CALLBACK_QUEUE_STANDARD) kullanırsanız, iş parçacığını hiçbir zaman engellemeniz kritik önem taşır, çünkü bu durum Media Foundation işlem hattının tamamının verileri işlemesini engelleyebilir. Tamamlanması engellenecek veya uzun sürecek bir işlem gerçekleştirmeniz gerekiyorsa, özel bir iş kuyruğu kullanın. Özel bir iş kuyruğu oluşturmak için MFAllocateWorkQueueçağrısında bulunur. G/Ç işlemlerini gerçekleştiren tüm işlem hattı bileşenleri aynı nedenle G/Ç çağrılarını engellemekten kaçınmalıdır. IMFByteStream arabirimi, zaman uyumsuz dosya G/Ç için yararlı bir soyutlama sağlar.

Başlat.../Bitir... Kalıbı Uygulama

Zaman Uyumsuz Yöntemleri Çağırma'de açıklandığı gibi, Media Foundation'daki zaman uyumsuz yöntemler genellikle Begin.../End.... desenini kullanır. Bu düzende, zaman uyumsuz bir işlem aşağıdakine benzer imzalara sahip iki yöntem kullanır:

// Starts the asynchronous operation.
HRESULT BeginX(IMFAsyncCallback *pCallback, IUnknown *punkState);

// Completes the asynchronous operation. 
// Call this method from inside the caller's Invoke method.
HRESULT EndX(IMFAsyncResult *pResult);

Yöntemi gerçekten zaman uyumsuz hale getirmek için, BeginX uygulamasının gerçek işi başka bir iş parçacığında gerçekleştirmesi gerekir. İş kuyruklarının ortaya çıktığı yer burasıdır. İzleyen adımlarda, çağıranıBeginX ve EndXçağıran koddur. Bu bir uygulama veya Media Foundation işlem hattı olabilir. bileşeni, BeginX ve EndXuygulayan koddur.

  1. Çağıran, çağıranın IMFAsyncCallback arabirimine bir işaretçi geçirerek begin... çağırır.
  2. Bileşen yeni bir zaman uyumsuz sonuç nesnesi oluşturur. Bu nesne, çağıranın geri çağırma arabirimini ve durum nesnesini depolar. Genellikle, bileşenin işlemi tamamlamak için ihtiyaç duyduğu tüm özel durum bilgilerini de depolar. Bu adımdaki sonuç nesnesi, sonraki diyagramda "Sonuç 1" olarak etiketlenmiştir.
  3. Bileşen ikinci bir sonuç nesnesi oluşturur. Bu sonuç nesnesi iki işaretçi depolar: İlk sonuç nesnesi ve çağıranın geri çağırma arabirimi. Bu sonuç nesnesi sonraki diyagramda "Sonuç 2" olarak etiketlenmiştir.
  4. Bileşen, yeni bir iş öğesini kuyruğa almak için MFPutWorkItemExçağırır.
  5. Invoke yönteminde, bileşen zaman uyumsuz çalışmayı yapar.
  6. Bileşen, çağıranın geri çağırma yöntemini çağırmak için MFInvokeCallbackçağırır.
  7. Çağıran EndX yöntemini çağırır.

Bir nesnenin başlangıç/bitiş desenini nasıl uyguladığını gösteren diyagram

Zaman Uyumsuz Yöntem Örneği

Bu tartışmayı göstermek için, eşleştirilmiş bir örnek kullanacağız. Karekök hesaplama için asenkron bir yöntem düşünün:

    HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
    HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);

x parametresi, BeginSquareRoot'nin karekökünün hesaplanacağı değerdir. Kare kök, pVal parametresi EndSquareRootiçinde döndürülür.

Bu iki yöntemi uygulayan bir sınıfın bildirimi aşağıdadır:

class SqrRoot : public IMFAsyncCallback
{
    LONG    m_cRef;
    double  m_sqrt;

    HRESULT DoCalculateSquareRoot(AsyncOp *pOp);

public:

    SqrRoot() : m_cRef(1)
    {

    }

    HRESULT BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState);
    HRESULT EndSquareRoot(IMFAsyncResult *pResult, double *pVal);

    // IUnknown methods.
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
    {
        static const QITAB qit[] = 
        {
            QITABENT(SqrRoot, IMFAsyncCallback),
            { 0 }
        };
        return QISearch(this, qit, riid, ppv);
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        LONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0)
        {
            delete this;
        }
        return cRef;
    }

    // IMFAsyncCallback methods.

    STDMETHODIMP GetParameters(DWORD* pdwFlags, DWORD* pdwQueue)
    {
        // Implementation of this method is optional.
        return E_NOTIMPL;  
    }
    // Invoke is where the work is performed.
    STDMETHODIMP Invoke(IMFAsyncResult* pResult);
};

SqrRoot sınıfı, karekök işlemini bir iş kuyruğuna koyabilmek için IMFAsyncCallback uygulamaktadır. DoCalculateSquareRoot yöntemi, karekökünü hesaplayan özel sınıf yöntemidir. Bu yöntem iş kuyruğu thread'inden çağrılacak.

Öncelikle, iş kuyruğu iş parçacığı SqrRoot::Invoke'yi çağırdığında xdeğerinin alınabilmesi için bu değeri depolamanın bir yolunu bulmalıyız. Bilgileri depolayan basit bir sınıf aşağıdadır:

class AsyncOp : public IUnknown
{
    LONG    m_cRef;

public:

    double  m_value;

    AsyncOp(double val) : m_cRef(1), m_value(val) { }

    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
    {
        static const QITAB qit[] = 
        {
            QITABENT(AsyncOp, IUnknown),
            { 0 }
        };
        return QISearch(this, qit, riid, ppv);
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        LONG cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0)
        {
            delete this;
        }
        return cRef;
    }
};

Bu sınıf, sonuç nesnesinde depolanabilmesi için IUnknown uygular.

Aşağıdaki kod BeginSquareRoot yöntemini uygular:

HRESULT SqrRoot::BeginSquareRoot(double x, IMFAsyncCallback *pCB, IUnknown *pState)
{
    AsyncOp *pOp = new (std::nothrow) AsyncOp(x);
    if (pOp == NULL)
    {
        return E_OUTOFMEMORY;
    }

    IMFAsyncResult *pResult = NULL;

    // Create the inner result object. This object contains pointers to:
    // 
    //   1. The caller's callback interface and state object. 
    //   2. The AsyncOp object, which contains the operation data.
    //

    HRESULT hr = MFCreateAsyncResult(pOp, pCB, pState, &pResult);

    if (SUCCEEDED(hr))
    {
        // Queue a work item. The work item contains pointers to:
        // 
        // 1. The callback interface of the SqrRoot object.
        // 2. The inner result object.

        hr = MFPutWorkItem(MFASYNC_CALLBACK_QUEUE_STANDARD, this, pResult);

        pResult->Release();
    }

    return hr;
}

Bu kod aşağıdakileri yapar:

  1. xdeğerini tutmak için AsyncOp sınıfının yeni bir örneğini oluşturur.
  2. Bir sonuç nesnesi oluşturmak için MFCreateAsyncResultçağırır. Bu nesne birkaç işaretçi barındırıyor:
    • Çağıranın IMFAsyncCallback arabirimine bir işaretçi.
    • Çağıranın durum nesnesinin işaretçisi (pState).
    • AsyncOp nesnesinin işaretçisi.
  3. Yeni bir iş öğesini kuyruğa almak için MFPutWorkItemçağırır. Bu çağrı örtük olarak aşağıdaki işaretçileri tutan bir dış sonuç nesnesi oluşturur:
    • SqrRoot nesnesinin IMFAsyncCallback arabiriminin işaretçisi.
    • 2. adımdaki iç sonuç nesnesinin işaretçisi.

Aşağıdaki kod SqrRoot::Invoke yöntemini uygular:

// Invoke is called by the work queue. This is where the object performs the
// asynchronous operation.

STDMETHODIMP SqrRoot::Invoke(IMFAsyncResult* pResult)
{
    HRESULT hr = S_OK;

    IUnknown *pState = NULL;
    IUnknown *pUnk = NULL;
    IMFAsyncResult *pCallerResult = NULL;

    AsyncOp *pOp = NULL; 

    // Get the asynchronous result object for the application callback. 

    hr = pResult->GetState(&pState);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pState->QueryInterface(IID_PPV_ARGS(&pCallerResult));
    if (FAILED(hr))
    {
        goto done;
    }

    // Get the object that holds the state information for the asynchronous method.
    hr = pCallerResult->GetObject(&pUnk);
    if (FAILED(hr))
    {
        goto done;
    }

    pOp = static_cast<AsyncOp*>(pUnk);

    // Do the work.

    hr = DoCalculateSquareRoot(pOp);

done:
    // Signal the application.
    if (pCallerResult)
    {
        pCallerResult->SetStatus(hr);
        MFInvokeCallback(pCallerResult);
    }

    SafeRelease(&pState);
    SafeRelease(&pUnk);
    SafeRelease(&pCallerResult);
    return S_OK;
}

Bu yöntem iç sonuç nesnesini ve AsyncOp nesnesini alır. Ardından AsyncOp nesnesini DoCalculateSquareRootgeçirir. Son olarak, durum kodunu ayarlamak için IMFAsyncResult::SetStatus çağırır ve çağıranın geri çağırma yöntemini çağırmak için MFInvokeCallback.

DoCalculateSquareRoot yöntemi tam olarak beklediğiniz şeyi yapar:

HRESULT SqrRoot::DoCalculateSquareRoot(AsyncOp *pOp)
{
    pOp->m_value = sqrt(pOp->m_value);

    return S_OK;
}

Çağıranın geri çağırma yöntemi çağrıldığında, End... yöntemini çağırmak çağıranın sorumluluğundadır; bu durumda EndSquareRoot. EndSquareRoot, arayanın zaman uyumsuz işlemin sonucunu nasıl elde ettiğini gösterir ve bu örnekte hesaplanan kare köktür. Bu bilgiler sonuç nesnesinde depolanır:

HRESULT SqrRoot::EndSquareRoot(IMFAsyncResult *pResult, double *pVal)
{
    *pVal = 0;

    IUnknown *pUnk = NULL;

    HRESULT hr = pResult->GetStatus();

    if (FAILED(hr))
    {
        goto done;
    }

    hr = pResult->GetObject(&pUnk);
    if (FAILED(hr))
    {
        goto done;
    }

    AsyncOp *pOp = static_cast<AsyncOp*>(pUnk);

    // Get the result.
    *pVal = pOp->m_value;

done:
    SafeRelease(&pUnk);
    return hr;
}

İşlem Kuyrukları

Şimdiye kadar, nesnenin geçerli durumundan bağımsız olarak zaman uyumsuz bir işlemin herhangi bir zamanda yapılabilmesi açıkça varsayılmıştır. Örneğin, aynı yönteme yönelik önceki bir çağrı hala beklemedeyken bir uygulama BeginSquareRoot çağırırsa ne olacağını göz önünde bulundurun. SqrRoot sınıfı, önceki iş öğesi tamamlanmadan önce yeni iş öğesini kuyruğa alabilir. Ancak, iş kuyruklarının iş öğelerini seri hale getirmesi garanti değildir. Hatırlayın ki bir iş kuyruğu, iş öğelerini göndermek için birden fazla iş parçacığı kullanabilir. Çok iş parçacıklı bir ortamda, bir iş öğesi, bir önceki iş öğesi tamamlanmadan çağrılabilir. Çağrı işlevi geri çağrılmadan hemen önce bir bağlam değişikliği gerçekleşirse, iş öğeleri sıra dışı bile çağrılabilir.

Bu nedenle, gerekirse işlemleri kendi üzerinde seri hale getirmek nesnenin sorumluluğundadır. Başka bir deyişle, B işlemin başlayabilmesi için nesnenin A işleminin tamamlanması gerekiyorsa, A tamamlanana kadar nesnenin B için bir iş öğesini kuyruğa almaması gerekir. Bir nesne, bekleyen işlemlerden oluşan kendi kuyruğuna sahip olarak bu gereksinimi karşılayabilir. Nesnede asenkron bir yöntem çağrıldığında, nesne isteği kendi kuyruğuna yerleştirir. Her uyumsuz işlem tamamlandığında, nesne kuyruktan bir sonraki isteği çeker. MPEG1Source Örneği, böyle bir kuyruğun nasıl uygulanacağına dair bir örnek gösterir.

Tek bir yöntem, özellikle G/Ç çağrıları kullanıldığında birkaç eş zamansız işlem içerebilir. Zaman uyumsuz yöntemler uyguladığınızda serileştirme gereksinimlerini dikkatle düşünün. Örneğin, önceki G/Ç isteği hala beklemedeyken nesnenin yeni bir işlem başlatması geçerli mi? Yeni işlem nesnenin iç durumunu değiştirirse, önceki G/Ç isteği tamamlanıp eski olabilecek verileri döndürdüğünde ne olur? İyi bir durum diyagramı geçerli durum geçişlerini tanımlamaya yardımcı olabilir.

İş Parçacıkları ve Süreçler Arası Dikkat Edilmesi Gerekenler

İş kuyrukları, arabirim işaretçilerini iş parçacığı sınırları boyunca sıralamak için COM hazırlamayı kullanmaz. Bu nedenle, bir nesne apartman iş parçacıklı olarak kaydedilmiş olsa veya uygulama iş parçacığı tek iş parçacıklı bir ortama (STA) girmiş olsa bile, IMFAsyncCallback geri çağırmaları farklı bir iş parçacığından çağrılacaktır. Her durumda, tüm Media Foundation işlem hattı bileşenleri "Her İkisi de" iş parçacığı modelini kullanmalıdır.

Media Foundation'daki bazı arabirimler, bazı zaman uyumsuz yöntemlerin uzak sürümlerini tanımlar. Bu yöntemlerden biri işlem sınırları arasında çağrıldığında, Media Foundation proxy/stub DLL yöntemin uzak sürümünü çağırır ve yöntem parametrelerinin özel hazırlamasını gerçekleştirir. Uzak işlemde saplama, çağrıyı nesnedeki yerel yönteme geri çevirir. Bu işlem hem uygulama hem de uzak nesne için saydamdır. Bu özel hazırlama yöntemleri öncelikle korumalı medya yoluna (PMP) yüklenen nesneler için sağlanır. PMP hakkında daha fazla bilgi için bkz, korumalı medya yolu.

Zaman Uyumsuz Geri Çağırma Yöntemleri

İş Kuyrukları