मैं कैसे async / डेडलॉक का निदान कर सकता हूँ?


24

मैं एक नए कोडबेस के साथ काम कर रहा हूं, जो एसिंक्स / वेट का भारी उपयोग करता है। मेरी टीम के अधिकांश लोग भी async / प्रतीक्षा में काफी नए हैं। हम आम तौर पर Microsoft द्वारा निर्दिष्ट सर्वोत्तम प्रथाओं को पकड़ते हैं , लेकिन आम तौर पर हमारे संदर्भ को async कॉल के माध्यम से प्रवाह करने की आवश्यकता होती है और पुस्तकालयों के साथ काम कर रहे हैं जो नहीं करते हैं ConfigureAwait(false)

उन सभी चीजों को मिलाएं और हम लेख में वर्णित async गतिरोध में भाग लेते हैं ... साप्ताहिक। वे यूनिट परीक्षण के दौरान दिखाई नहीं देते हैं, क्योंकि हमारे नकली डेटा स्रोत (आमतौर पर Task.FromResult) गतिरोध को ट्रिगर करने के लिए पर्याप्त नहीं हैं। इसलिए रनटाइम या इंटीग्रेशन टेस्ट के दौरान, कुछ सर्विस कॉल सिर्फ लंच के लिए जाती हैं और कभी नहीं लौटती हैं। यह सर्वर को मारता है, और आम तौर पर चीजों को गड़बड़ करता है।

समस्या यह है कि जहां गलती की गई थी, उसे ट्रैक करना (आमतौर पर सभी तरह से अनिवार्य नहीं है) आमतौर पर मैनुअल कोड निरीक्षण शामिल होता है, जो समय लेने वाला और स्वचालित करने में सक्षम नहीं होता है।

गतिरोध के कारण का निदान करने का एक बेहतर तरीका क्या है?


1
अच्छा प्रश्न; मैंने खुद यह सोचा है। क्या आपने इस आदमी के asyncलेखों का संग्रह पढ़ा है ?
रॉबर्ट हार्वे

@ रोबर्टहवे - शायद सभी नहीं, लेकिन मैंने कुछ पढ़ा है। अधिक "हर जगह इन दो / तीन चीजों को करना सुनिश्चित करें अन्यथा आपका कोड रनटाइम में एक भयानक मौत मर जाएगा"।
तेलस्टिन

क्या आप async छोड़ने या सबसे अधिक लाभकारी बिंदुओं तक इसके उपयोग को कम करने के लिए खुले हैं? Async IO सभी या कुछ भी नहीं है।
usr

1
यदि आप गतिरोध को पुन: उत्पन्न कर सकते हैं, तो क्या आप ब्लॉकिंग कॉल को देखने के लिए स्टैक ट्रेस को नहीं देख सकते हैं?
svick

2
यदि समस्या "सभी तरह से समान नहीं है", तो इसका मतलब है कि गतिरोध का एक आधा एक पारंपरिक गतिरोध है और इसे सिंक्रनाइज़ेशन संदर्भ थ्रेड के स्टैक ट्रेस में दिखाई देना चाहिए।
svick

जवाबों:


4

ठीक है - मुझे यकीन नहीं है कि निम्न में से कोई भी आपकी मदद करेगा, क्योंकि मैंने एक समाधान विकसित करने में कुछ धारणाएं बनाई हैं जो आपके मामले में सच हो सकती हैं या नहीं। शायद मेरा "समाधान" बहुत सैद्धांतिक है और केवल कलात्मक उदाहरणों के लिए काम करता है - मैंने नीचे के सामान से परे कोई परीक्षण नहीं किया है।
इसके अलावा, मैं एक वास्तविक समाधान की तुलना में निम्नलिखित अधिक वैकल्पिक हल देखूंगा, लेकिन प्रतिक्रियाओं की कमी को देखते हुए मुझे लगता है कि यह अभी भी कुछ भी नहीं से बेहतर हो सकता है (मैं आपके प्रश्न को एक समाधान के लिए इंतजार कर रहा देखता रहा, लेकिन एक पोस्ट किए जाने को देखते हुए मैंने खेलना शुरू नहीं किया मुद्दे के साथ आसपास)।

लेकिन पर्याप्त कहा: मान लें कि हमारे पास एक सरल डेटा सेवा है जिसका उपयोग पूर्णांक प्राप्त करने के लिए किया जा सकता है:

public interface IDataService
{
    Task<int> LoadMagicInteger();
}

एक साधारण कार्यान्वयन अतुल्यकालिक कोड का उपयोग करता है:

public sealed class CustomDataService
    : IDataService
{
    public async Task<int> LoadMagicInteger()
    {
        Console.WriteLine("LoadMagicInteger - 1");
        await Task.Delay(100);
        Console.WriteLine("LoadMagicInteger - 2");
        var result = 42;
        Console.WriteLine("LoadMagicInteger - 3");
        await Task.Delay(100);
        Console.WriteLine("LoadMagicInteger - 4");
        return result;
    }
}

अब, एक समस्या उत्पन्न होती है, अगर हम इस वर्ग के अनुसार "गलत तरीके से" कोड का उपयोग कर रहे हैं। आईएनजी परिणाम के बजाय Fooगलत तरीके से पहुंचता है:Task.ResultawaitBar

public sealed class ClassToTest
{
    private readonly IDataService _dataService;

    public ClassToTest(IDataService dataService)
    {
        this._dataService = dataService;
    }

    public async Task<int> Foo()
    {
        var result = this._dataService.LoadMagicInteger().Result;
        return result;
    }
    public async Task<int> Bar()
    {
        var result = await this._dataService.LoadMagicInteger();
        return result;
    }
}

हमें (आपको) अब एक परीक्षण लिखने का एक तरीका है जो कॉल करते समय सफल होता है Barलेकिन कॉल करते समय विफल हो जाता हैFoo (कम से कम अगर मैं प्रश्न सही ढंग से ;-))।

मैं कोड बोलने दूँगा; यहाँ मैं क्या कर रहा था (विज़ुअल स्टूडियो परीक्षणों का उपयोग करके, लेकिन यह NUnit का उपयोग करके भी काम करना चाहिए):

DataServiceMockउपयोग करता है TaskCompletionSource<T>। यह हमें परीक्षण रन में एक परिभाषित बिंदु पर परिणाम निर्धारित करने की अनुमति देता है जो निम्न परीक्षण की ओर जाता है। ध्यान दें कि हम TaskCompletionSource को वापस परीक्षण में पास करने के लिए एक प्रतिनिधि का उपयोग कर रहे हैं। आप इसे परीक्षण की प्रारंभिक विधि में भी डाल सकते हैं और गुणों का उपयोग कर सकते हैं।

TaskCompletionSource<int> tcs = null;
this._dataService.LoadMagicIntegerMock = t => tcs = t;

Task<int> task = null;
TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());

tcs.TrySetResult(42);

var result = task.Result;
Assert.AreEqual(42, result);

this._end = true;

यहां क्या हो रहा है कि हम पहले यह सत्यापित करते हैं कि हम विधि को अवरुद्ध किए बिना छोड़ सकते हैं (यह काम नहीं करेगा यदि कोई एक्सेस करता है Task.Result- इस मामले में हम एक टाइमआउट में भाग लेंगे क्योंकि कार्य का परिणाम उपलब्ध नहीं है जब तक कि विधि वापस नहीं आई है। )।
फिर, हम परिणाम सेट करते हैं (अब विधि निष्पादित कर सकते हैं) और हम परिणाम को सत्यापित करते हैं (एक इकाई परीक्षण के अंदर हम टास्क तक पहुंच सकते हैं। परिणाम के रूप में हम वास्तव में अवरुद्ध होने के लिए चाहते हैं)।

पूर्ण परीक्षण वर्ग - BarTestसफल और FooTestवांछित के रूप में विफल रहता है।

[TestClass]
public class UnitTest1
{
    private DataServiceMock _dataService;
    private ClassToTest _instance;
    private bool _end;

    [TestInitialize]
    public void Initialize()
    {
        this._dataService = new DataServiceMock();
        this._instance = new ClassToTest(this._dataService);

        this._end = false;
    }
    [TestCleanup]
    public void Cleanup()
    {
        Assert.IsTrue(this._end);
    }

    [TestMethod]
    public void FooTest()
    {
        TaskCompletionSource<int> tcs = null;
        this._dataService.LoadMagicIntegerMock = t => tcs = t;

        Task<int> task = null;
        TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Foo());

        tcs.TrySetResult(42);

        var result = task.Result;
        Assert.AreEqual(42, result);

        this._end = true;
    }
    [TestMethod]
    public void BarTest()
    {
        TaskCompletionSource<int> tcs = null;
        this._dataService.LoadMagicIntegerMock = t => tcs = t;

        Task<int> task = null;
        TaskTestHelper.AssertDoesNotBlock(() => task = this._instance.Bar());

        tcs.TrySetResult(42);

        var result = task.Result;
        Assert.AreEqual(42, result);

        this._end = true;
    }
}

और गतिरोध / टाइमआउट के लिए परीक्षण करने के लिए एक छोटा सहायक वर्ग:

public static class TaskTestHelper
{
    public static void AssertDoesNotBlock(Action action, int timeout = 1000)
    {
        var timeoutTask = Task.Delay(timeout);
        var task = Task.Factory.StartNew(action);

        Task.WaitAny(timeoutTask, task);

        Assert.IsTrue(task.IsCompleted);
    }
}

अच्छा जवाब। जब मैं कुछ समय होता है तो मैं स्वयं आपके कोड की कोशिश करने की योजना बना रहा हूं (मुझे वास्तव में यह पता नहीं है कि यह काम करता है या नहीं), लेकिन यश और प्रयास के लिए एक उत्थान।
रॉबर्ट हार्वे

-2

यहां एक रणनीति है जो मैंने एक विशाल और बहुत, बहुत बहुपरत एप्लिकेशन में उपयोग की है:

सबसे पहले, आपको म्यूटेक्स (दुर्भाग्य से) के आसपास कुछ डेटा संरचना की आवश्यकता है और कॉल निर्देशिका को सिंक्रनाइज़ नहीं करना है। उस डेटा संरचना में, पहले से बंद म्यूटेक्स का लिंक है। प्रत्येक म्यूटेक्स में 0 से शुरू होने वाला "स्तर" होता है, जिसे आप म्यूटेक्स बनाते समय असाइन करते हैं और कभी भी बदल नहीं सकते।

और नियम यह है: यदि एक म्यूटेक्स बंद है, तो आपको कभी भी अन्य म्यूटेक्स को निचले स्तर पर लॉक करना होगा। यदि आप उस नियम का पालन करते हैं, तो आपके पास गतिरोध नहीं हो सकते। जब आप उल्लंघन पाते हैं, तो आपका आवेदन अभी भी ठीक है और ठीक चल रहा है।

जब आप उल्लंघन पाते हैं, तो दो संभावनाएं हैं: आपने स्तरों को गलत ठहराया हो सकता है। आपने A को B के बाद लॉक किया है, इसलिए B का स्तर कम होना चाहिए था। तो आप स्तर को ठीक करें और फिर से प्रयास करें।

दूसरी संभावना: आप इसे ठीक नहीं कर सकते। आपका कुछ कोड A को लॉक करने के बाद B को लॉक करता है, जबकि कुछ अन्य कोड B से लॉक करने के बाद A को अनुमति देने के लिए स्तरों को असाइन करने का कोई तरीका नहीं है। और निश्चित रूप से यह एक संभावित गतिरोध है: यदि दोनों कोड अलग-अलग थ्रेड्स पर एक साथ चलते हैं, तो गतिरोध की संभावना है।

इसे शुरू करने के बाद, बल्कि एक छोटा चरण था जहां स्तरों को समायोजित किया जाना था, इसके बाद एक लंबा चरण था जहां संभावित गतिरोध पाए गए थे।


4
मुझे क्षमा करें, यह कैसे async / प्रतीक्षा व्यवहार पर लागू होता है? मैं वास्तविक रूप से टास्क समानांतर लाइब्रेरी में कस्टम म्यूटेक्स प्रबंधन संरचना को इंजेक्ट नहीं कर सकता।
तेलस्तीन

-3

क्या आप Async / Await का उपयोग कर रहे हैं ताकि आप किसी डेटाबेस की तरह महंगी कॉल को समानांतर कर सकें? डीबी में निष्पादन पथ के आधार पर यह संभव नहीं हो सकता है।

Async / इंतजार के साथ टेस्ट कवरेज चुनौतीपूर्ण हो सकता है और बग को खोजने के लिए वास्तविक उत्पादन उपयोग जैसा कुछ भी नहीं है। एक पैटर्न जिस पर आप विचार कर सकते हैं वह एक सहसंबंध आईडी से गुजर रहा है और इसे स्टैक के नीचे लॉग कर रहा है, फिर एक कैस्केडिंग टाइमआउट है जो त्रुटि को लॉग करता है। यह SOA पैटर्न से अधिक है, लेकिन कम से कम यह आपको यह एहसास दिलाएगा कि यह कहां से आ रहा है। हमने गतिरोध का पता लगाने के लिए स्प्लंक के साथ इसका इस्तेमाल किया।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.